Compare commits

..

693 Commits

Author SHA1 Message Date
bruno DA SILVA
46151c87c0 N°2888 Check password policy only if field set with a string
Some callers are setting the field using an ormPassword object containing hashed password + salt

examples:
 - csv import
 - data synchro
 - ...
2020-03-25 12:43:41 +01:00
Pierre Goiffon
75a900c6f8 🚀 Tool to batch update XML datamodel version 2020-03-24 16:46:48 +01:00
Molkobain
e8c9d99783 Increase XML version to v1.7 2020-03-24 14:06:41 +01:00
Eric
1600302ad9 N°2869 - Removed Check for bad finalclass in root classes (already done) 2020-03-19 15:40:52 +01:00
Eric
a9c3a1b782 N°2869 - Check for bad finalclass in root classes (Allow all non-abstract child classes) 2020-03-19 10:01:16 +01:00
Eric
74848254a4 N°2869 - Check for bad finalclass in root classes 2020-03-18 14:10:35 +01:00
Eric
d7d9bfe0fd N°2869 - Check for bad finalclass in intermediate classes 2020-03-18 10:09:05 +01:00
odain
dd96dec100 Fix license file generation; exclude itop-portal-base 2020-03-18 08:42:55 +01:00
Pierre Goiffon
16ff51f3b7 📄 Update licenses after generation tool upgrade
See 76d26e8e
2020-03-18 08:15:09 +01:00
Pierre Goiffon
27c651b33c 📄 Remove itop-portal-base from license file 2020-03-17 18:08:49 +01:00
Pierre Goiffon
32375265cb 📄 Remove 2.x/authent-cas from license file 2020-03-17 17:47:25 +01:00
Pierre Goiffon
0cba163dc9 🔖 Update version to final in iTop files 2020-03-17 16:50:45 +01:00
Pierre Goiffon
fd1e17cc32 🚀 Release tool to update versions 2020-03-17 16:49:05 +01:00
Eric
d85e1906b7 N°2746 - New Attribute Enum Set
XML migration from 1.7 to 1.6
2020-03-17 12:03:10 +01:00
Pierre Goiffon
f8df84aa7b Update dict for 2.7.0-RC 2020-03-17 10:55:53 +01:00
Eric
c26b9459bb N°2869 - Fix 2.7 Migration
Run UPDATE requests just after the corresponding ALTER TABLE requests
2020-03-16 18:49:12 +01:00
Molkobain
4f7676c42d N°2735 - Rollback previous "fixes" to keep the simple ID policy in the Designer and a unique ID generation at runtime 2020-03-16 12:17:09 +01:00
Molkobain
ceddafaebe N°2735 - Rename parameter for better consistency 2020-03-16 12:17:09 +01:00
Stephen Abello
950640babe N°1986 - Revert feature 2020-03-13 10:24:25 +01:00
jbostoen
11e6be1037 🌐 Added NL translations (#124)
Co-authored-by: jbostoen <->
2020-03-13 09:42:37 +01:00
Molkobain
29d963317f N°2735 - Fix dashlet ID generation to have the "CUSTOM" prefix only at runtime 2020-03-12 16:46:15 +01:00
Molkobain
dd300e075c N°2735 - Fix dashlet edition in the Designer (property form ID was not matching dashlet's) 2020-03-12 16:46:03 +01:00
Molkobain
774ace2302 Fix icon select widget to be compatible with iTop 2.7 2020-03-12 14:16:03 +01:00
Molkobain
bbfddea93d Open new_dashlet_id operation for Designer 2020-03-12 14:16:02 +01:00
Stephen Abello
c5c7fd5c85 N°2855 - Security hardening 2020-03-12 14:13:17 +01:00
jbostoen
5d4b9f4a89 🌐 Fix typos in English translation (#123) 2020-03-12 08:51:40 +01:00
Stephen Abello
d01caaf4e4 N°2853 - Security hardening 2020-03-10 10:23:38 +01:00
Pierre Goiffon
f895821db9 ⚗️ CONTRIBUTING : added some emoji O:) 2020-03-06 20:52:33 +01:00
bruno DA SILVA
19f34d1a72 composer reflexion: list outdated packages 2020-03-05 11:33:36 +01:00
Pierre Goiffon
7ff1a03a3c N°2820 monthly log rotation : restore default config 2020-03-04 16:04:19 +01:00
Pierre Goiffon
eadc3b72c2 📝 N°2793 log rotation add PHPDoc about timezones 2020-03-04 14:23:34 +01:00
odain
c06f8e9a98 N°2793 log rotation test : fix timezone issues 2020-03-04 12:05:42 +01:00
Pierre Goiffon
6675d7d42a N°2793 Test log rotation 2020-03-04 09:21:05 +01:00
Eric
afc118e9c2 🐛 fix GetAsPlainText() on EnumSet 2020-03-03 17:34:15 +01:00
Pierre Goiffon
f36fcb2a2d N°2820 Log rotation : change default from weekly to monthly 2020-03-03 15:31:11 +01:00
Eric
f062af367d N°2826 - Bad SQL request for group by with data-localizer
Unit tests to check the fix in data-localizer
2020-03-03 15:25:12 +01:00
Pierre Goiffon
29d24faf52 N°2793 Log rotation : fix no rotation :/
Was caused by erroneous file exists test
2020-03-03 10:18:09 +01:00
Pierre Goiffon
33f3f2810e N°2793 Log rotation : add file exists check in the lock 2020-03-02 18:33:00 +01:00
Pierre Goiffon
fad00200b6 🔧 PHPStorm remove is_null() rewrite inspection 2020-03-02 15:56:26 +01:00
Pierre Goiffon
56ef6feadf N°2820 Log rotation : new MonthlyRotatingLogFileNameBuilder class 2020-03-02 15:52:59 +01:00
Pierre Goiffon
2be16f9078 N°2793 Log rotation (#117)
Now log file name is unchanged : current log is still /log/error.log \o/

Rotation check (using file last modification time) is done :
* on each file write : we don't want to miss calls if session last from 23:59:59 to 00:01 for example ! Though the filemtime() call is done once per session to lower performance impacts
* using a new background process (LogFileRotationProcess)

File renaming on setup is therefore removed.
Also the interface is renamed (from ILogFileNameBuilder to iLogFileNameBuilder) to conform to iTop convention.
2020-03-02 15:01:12 +01:00
Eric
6874aed4a2 N°1627 - Ticket ref sometimes duplicate
add MakeInsertQuery() to legacy
2020-03-02 12:04:12 +01:00
Pierre Goiffon
07b8830436 N°2814 Fix cannot authenticate in some HTTP calls
basic mode was forced in 0dd1f26b
scripts concerned :
* synchro/synchro_import.php
* webservices/cron.php
* webservices/import.php
2020-03-02 11:56:00 +01:00
Molkobain
39d3e00ba1 N°2822 - Fix timeout message through AJAX calls in the Portal 2020-02-28 16:53:16 +01:00
Stephen Abello
ffa43160bf N°1164 #1491 - Add padding and border to code blocks 2020-02-28 16:42:46 +01:00
Pierre Goiffon
a45d1336f4 🎨 Change \ormStopWatch::ComputeGoal for IDE convencience, add phpdoc 2020-02-28 12:02:20 +01:00
Federico Lazcano
5a287fabba 🌐 Typo in ES_CR User Requests 2020-02-28 08:23:34 +01:00
Federico Lazcano
da86ee4114 🌐 Typo in ES_CR Incidents 2020-02-28 08:22:59 +01:00
Federico Lazcano
5157788afe 🌐 Typo in ES_CR User Requests 2020-02-28 08:22:14 +01:00
Molkobain
386e25efd6 N°2314 - Remove basque-red, ocean-blue and test-blue from default themes 2020-02-27 15:41:54 +01:00
Molkobain
649e2f8e6a Internal: Remove unused import 2020-02-27 15:10:41 +01:00
Molkobain
3c3d744747 N°2314 - Refactor part of the compilation in dedicated helpers 2020-02-27 15:09:57 +01:00
Molkobain
1371eee826 N°2735 - Continue rework of the dashlet id generation: Dashlet could not be added in the Designer 2020-02-27 11:54:20 +01:00
Molkobain
6645a5053f N°2806 - Fix errors on legacy portal "portal" tag during migration to iTop 2.7 2020-02-26 17:17:56 +01:00
Molkobain
e2a3e0e74f N°2735 - Continue rework of the dashlet id generation:
- Move generation from DashboardLayout to Dashboard
- Migrate dashlet user preference in RuntimeDashboard only (and not in DesignTimeDashboard)
2020-02-26 16:29:32 +01:00
Molkobain
401f82062a N°2735 - Make sure to always have the dashboard (sanitized) id for dashlets rendering 2020-02-26 12:10:18 +01:00
Molkobain
5a01a76f80 N°2735 - Add new sanitize filter ('element_identifier') for dashboard identifier 2020-02-26 12:10:18 +01:00
Pierre Goiffon
3e5520d079 N°2735 Fix new dashlet id didn't contain dashboard id 2020-02-26 09:13:19 +01:00
Pierre Goiffon
beef2a89a3 N°2684 Remove upgrade from another repository
This upgrade procedure was :
* dangerous : running two iTop of different versions on the same database should not be done
* insufficient : just /extensions/* was copied, not any Hub or Designer data, no log, no instance.txt, ...
2020-02-25 18:01:59 +01:00
Molkobain
2f920cbb46 Internal: PHPDoc and warnings suppression 2020-02-25 17:45:18 +01:00
Pierre Goiffon
feae36e5b8 N°2735 Fix dashlet id duplicates when moving dashlet from one cell to another 2020-02-25 15:43:20 +01:00
Stephen Abello
92ae0e72e1 N°2314 - Markup extensibility: Add a variable for hovered table lines background color 2020-02-25 15:14:12 +01:00
Stephen Abello
ed030403aa N°2112 - Remove unused legacy portal conf variable and its usage 2020-02-25 14:00:58 +01:00
Pierre Goiffon
dfc894f6fd N°2735 Fix cannot edit new dashlet properties regression
Was introduced by cf83bc73
2020-02-25 11:17:52 +01:00
Stephen Abello
368b49ef8f N°2314 - Markup extensibility: Fix table sorter icons in html export pages 2020-02-25 10:36:56 +01:00
Stephen Abello
ccfd3848fb N°1164 #1491 - Fix syntax code highlighting display in CaseLog/HTML fields 2020-02-25 09:54:18 +01:00
Molkobain
ea59f7bc23 N°2314 - Markup extensibility: Add metadata to caselogs in the admin. console 2020-02-24 18:22:01 +01:00
Molkobain
9d6ed7f489 N°2806 - Fix errors on legacy portal constants during migration to iTop 2.7 2020-02-24 17:03:13 +01:00
Molkobain
0aa006f7c4 Internal: Fix typo in PHPDoc 2020-02-24 16:47:10 +01:00
Molkobain
c669d6951b PHPDoc and warnings suppression 2020-02-24 16:36:31 +01:00
Molkobain
9412f260ae PHPDoc 2020-02-24 11:01:24 +01:00
Molkobain
9781a11988 N°2803 - Regression: Fix "forgot_password" parameter not working anymore 2020-02-24 10:58:24 +01:00
Molkobain
1ed0210fe2 N°2799 - Fix double encoding in "top-list" display mode of the ManageBrick 2020-02-24 09:47:22 +01:00
Molkobain
1ce5ec73ea N°2798 - Fix unable to submit portal forms (Regression from dba6e8ce) 2020-02-24 09:24:23 +01:00
Eric
98304e2bda N°2596 - Allow '1' as true value for boolean in XML files 2020-02-21 18:14:03 +01:00
Pierre Goiffon
04fc58b55c 📝 Some @since annotations were missing complete version (ex 2.5 instead of 2.5.0) 2020-02-21 18:05:30 +01:00
Eric
096c3a3f13 N°2772 - Revert the loading of JS Dict in setup pages 2020-02-21 17:15:12 +01:00
Eric
87e22163d7 N°2037 - Add Twig template rendering to the WebPage 2020-02-21 14:35:25 +01:00
acognet
4cc8b89f4e N°2037 - New dashlet Gantt - add method to insert twig in an existing page 2020-02-21 12:09:15 +01:00
Pierre Goiffon
19809249a2 📝 Update PHPDoc for StopWatches interfaces 2020-02-20 18:01:09 +01:00
Molkobain
7347eed3ac PHPDoc 2020-02-20 17:43:14 +01:00
Molkobain
69d816e345 N°2275 - Add XML delta cleanup on datamodel BC breaking changes introduced in 2.7.0 2020-02-20 17:43:14 +01:00
Vincent Dumas
4008cb7688 Add blocks to enable customization 2020-02-20 17:18:39 +01:00
Lars Hippler
41a1bede70 🌐 Update DE-dictionary for iTop 2.7.0 (#113)
Many thanks @r0ert !
2020-02-20 16:04:24 +01:00
odain
e12845e412 N°2651 - Remove test directories from lib 2020-02-20 15:03:36 +01:00
odain
b30ad45792 N°2651 - Fix missing autoload 2020-02-20 14:58:39 +01:00
odain
84a11fb3c1 added namespace + mv iTopComposer + optimize FileIterator 2020-02-20 14:56:08 +01:00
Pierre Goiffon
ee39a387db N°2651 Remove tests from lib : browse dirs using SPL classes instead of GLOB 2020-02-20 14:56:08 +01:00
bruno DA SILVA
e3c6ac814e N°2651 - Removal of "Test" dirs within dependencies handled using composer 2020-02-20 14:56:08 +01:00
Eric
d668d65c70 N°2772 - Fix errors during upgrade. Prevent JS Dict load for setup pages. 2020-02-20 14:45:39 +01:00
Pierre Goiffon
e21e7c9cf0 🌐 N°2795 Fix dict typos 2020-02-20 09:36:33 +01:00
bruno DA SILVA
27a0de1da1 N°2154 - fix server crash in rare cases
Under undetermined circumstances, `exec('php -v')` called the current script triggering an infinite loop crashing the server
problem reported by @molkobain
see: https://stackoverflow.com/questions/43728378/running-php-files-through-shell-exec
2020-02-19 15:43:33 +01:00
Molkobain
d76e54996c PHPDoc 2020-02-19 11:54:50 +01:00
Pierre Goiffon
a4710f7542 N°2760 Abstract classes for extension API interfaces : remove return; for @return void methods 2020-02-18 18:15:45 +01:00
Eric
98a9c680c5 🐛 Updated rest example 2020-02-18 17:02:13 +01:00
Pierre Goiffon
a92157f763 N°2790 fix collapsibleLabel
* change icon when label closed
* fix switch in about dialog for licenses details
2020-02-18 16:34:51 +01:00
bruno DA SILVA
412f1a394f N°2574 - 💚 fix unit test
The behaviours has changed since the "password_renewed_date" is not changed only after the inter/update and no more just aftyer the $oUserLocal->Set('password')
2020-02-18 14:49:53 +01:00
Molkobain
dba6e8ce1a Fix images being too wide in HTML fields and caselogs in the end-users portal
Regression introduced in a previous version of iTop.
2020-02-17 16:29:21 +01:00
Molkobain
a127ca9ca0 N°2313 - Fix regression: No more validation message on password update in the end-users portal 2020-02-17 15:51:38 +01:00
Molkobain
0b5ee1e05c Internal: Fix typo in PHPDoc 2020-02-17 11:24:20 +01:00
Eric
f94e86ecea 🐛 Add missing function 2020-02-14 17:18:00 +01:00
Pierre Goiffon
fe770f36c5 N°2634 / N°2735 Migrate dashlet user prefs to new dashlet ID format 2020-02-14 15:59:09 +01:00
Pierre Goiffon
cf83bc7364 N°2634 / N°2735 Fix dashlets identifiers : was causing prb on widget init, prefs save
Dashlet id now includes :
* "CUSTOM-" if dashlet is contained in a custom dashboard, nothing elsewhere
* the ID of the dashboard
  - for menus : menu id escaped for HTML
  - for AttributeDashboard : <class>__<field>
* the row / cell / dashlet idx

Examples :
CUSTOM-UserRequestOverview_IDrow1-col0-0
Organization__overview_IDrow1-col0-12
2020-02-14 15:59:09 +01:00
Eric
76982a2846 Revert Last change. The values are already protected at this stage. 2020-02-14 15:42:05 +01:00
bruno DA SILVA
4cedd30625 N°2574 - bugfix and UI
- 🐛 fix regression preventing automatic update of password_renewed_date
 - 💄 add a "general information" fieldset
2020-02-13 15:23:56 +01:00
bruno DA SILVA
a86079c477 N°2154 - 🐛 fix an awful typo producing a nonsense
I'm sorry!
2020-02-13 15:21:01 +01:00
Eric
128a237392 N°2746 - Fix Tags configuration screen (removed EnumSet from tag editable list) 2020-02-13 12:31:22 +01:00
Eric
0ecfffe413 N°2746 - Fix export separator 2020-02-13 12:12:35 +01:00
Eric
ef3bdd63a4 N°2746 - Fix search from shortcut 2020-02-13 11:56:19 +01:00
Eric
585135c6c7 N°2758 - Keep AddCondition to avoid BC break 2020-02-13 11:56:01 +01:00
Eric
b3faa96a45 🌐 Add Trigger context label 2020-02-13 09:53:55 +01:00
Eric
6f04525cdf 🎨 cleanup code 2020-02-13 09:49:58 +01:00
Pierre Goiffon
03834fedb8 N°2369 deprecate MySQL views 2020-02-12 18:11:12 +01:00
Vincent Dumas
6bde8e867f Move menu "Universal Search" under "Query" 2020-02-12 18:01:38 +01:00
Molkobain
0e3d195250 N°2275 - Fix XML delta computation putting flags on wrong XML levels 2020-02-12 17:40:53 +01:00
Pierre Goiffon
fae8c9edbd N°2780 Add ContextTag::TAG_CONSOLE for ajax operations 2020-02-12 17:20:10 +01:00
Pierre Goiffon
133d267aca N°2329 Update TCPDF to version fixing unlink bug
Was updated to 6.3.2 fot PHP 7.4 compat, but this version had a regression (issue 159 in the original repo)
This commit integrates 6.3.4 that includes a fix for issue 159
2020-02-12 15:23:57 +01:00
Stephen Abello
166986f336 N°2314 - Markup extensibility: Replace some hardcoded values by overloadable variables 2020-02-12 14:53:19 +01:00
Stephen Abello
f76d649d1a Wee cleanup 2020-02-12 14:53:19 +01:00
Stephen Abello
30747b92c7 N°2755 - Security hardening 2020-02-12 14:53:19 +01:00
Stephen Abello
12ce718662 Internal: Add HtmlEntityDecode() to utils, a counterpart to HtmlEntities() 2020-02-12 14:53:19 +01:00
Molkobain
a1cdb46663 Internal: Refactor newsroom SCSS rules to real SCSS 2020-02-12 14:20:27 +01:00
Pierre Goiffon
824d8398a3 N°2634 / N°2735 Allow saving list prefs for all DashletObjectList
The id generated for the dashlets in the markup is the one used in the saved appUserPreferences. As no control is done during compilation nor in the Designer editor, we could have duplicates.
The first fix (081ba68a) was adding a generated suffix, but for default dashlet this was generated each time so the id was different on every page load ! For custom dashlets as their definition was saved in a XML file it was ok.
This new fix adds a prefix containing row and col id, so every time the id is the same. No duplicates should be found in the same cell.
2020-02-12 14:07:57 +01:00
Eric
406774aa15 N°2746 - Fix Import/Export using labels or code 2020-02-12 12:08:40 +01:00
Pierre Goiffon
dd8712e2e8 📝 Add more doc for \DBObject::GetAsHTML 2020-02-12 11:44:00 +01:00
Eric
767bcdf117 N°2746 - Fix unit tests (typo) 2020-02-11 17:00:55 +01:00
Eric
5e060737df N°2746 - Fix unit tests 2020-02-11 16:46:24 +01:00
Eric
d9bf0fe012 N°2746 - Fix breadcrumb for search of enumSet 2020-02-11 16:02:55 +01:00
Eric
93c9783b1a N°2746 - Fix empty search for TagSet 2020-02-11 14:51:22 +01:00
Eric
e9c1467026 N°2746 - Fix "Modify All" fatal error 2020-02-11 14:35:38 +01:00
Eric
863cb4cad6 N°2758 - Allow only one condition on ValueSetDef and restore cache 2020-02-11 11:54:00 +01:00
Pierre Goiffon
94b70fc473 N°2776 ObjectFormManager : change transaction scope
* move ApplyStimulus & triggers out of the transaction
* move \utils::RemoveTransaction to a finally block
2020-02-11 11:36:59 +01:00
Pierre Goiffon
4dc383cba8 N°2684 Fix setup broke when upgrading with a config file from another directory
In the moduleschoice screens we were using a wrong approot_url !
2020-02-11 09:41:48 +01:00
Pierre Goiffon
55d8a2316a 📝 GetAttributeFlags PHPDoc revised 2020-02-11 08:34:57 +01:00
Stephen Abello
fe8f274c14 N°2314 - Markup extensibility: Replace a hardcoded value by an overloadable variable 2020-02-10 15:33:47 +01:00
Stephen Abello
72fad49c4e N°2314 - Markup extensibility: Add default color to body node 2020-02-10 15:33:47 +01:00
Eric
888d0775e6 N°2758 - Removed ValueSetDef cache 2020-02-10 14:29:28 +01:00
Molkobain
db19f71758 N°2771 - Fix "Unknown form type" when changing user language in portal 2020-02-10 14:20:11 +01:00
Molkobain
a259443735 N°2314 - Markup extensibility: Add attribute flags as metadata to object forms 2020-02-10 13:27:36 +01:00
Pierre Goiffon
58e8ca1f50 📝 GetAttributeFlags PHPDoc 2020-02-10 09:56:31 +01:00
Pierre Goiffon
ab79426508 N°2293 some PHPDoc update 2020-02-07 18:21:02 +01:00
bruno DA SILVA
7e61917521 N°524 - password validity message can be superseded with conf 2020-02-07 17:25:17 +01:00
Molkobain
e42aab30a5 Internal: Fix regression introduced in 3d2a844f ("Close" button always displayed in object forms in IE) 2020-02-07 17:22:41 +01:00
Molkobain
a79ef0bd51 Update (massively) translations before iTop 2.7 release 2020-02-07 16:51:21 +01:00
Eric
5d88391109 N°2758 - Reset ValueSetDef cache when modifying some parameters 2020-02-07 14:27:22 +01:00
Molkobain
c56c04d84d N°2760 - Ease API interfaces implementation through abstract classes 2020-02-06 18:09:59 +01:00
Molkobain
f2b8f50a94 Internal: Add Anne to the sample data to welcome her! 👋 2020-02-06 18:07:51 +01:00
Molkobain
9de11a29fb PHPDoc 2020-02-06 16:25:25 +01:00
Molkobain
6537e00453 Internal: Add Matthieu to the sample data to welcome him! 👋 2020-02-06 15:53:26 +01:00
Eric
dd5f4909da Fix warning 2020-02-06 15:01:02 +01:00
Stephen Abello
ed67df734f N°2755 - Security hardening 2020-02-06 14:50:27 +01:00
Stephen Abello
44894526f1 N°2742 - HTML files preview are now raw text only 2020-02-06 14:27:13 +01:00
xtophe38
de78963b30 Fix DataAdministration translation to be aligned with other menus 2020-02-06 14:25:52 +01:00
bruno DA SILVA
948fd6f0ce N°2154 - improve robustness of submitted config validator
thanks to @molkobain 's awful provider's using PHP 4.4 within CLI
2020-02-06 14:07:39 +01:00
bruno DA SILVA
214dbeef5b N°2154 - var into string patterns can now also be enabled using server vars
- usage: $_SERVER['ITOP_CONFIG_PLACEHOLDERS']
 - plus removal of useless log Trace since this code is too early in iTop's init process for this feature
2020-02-06 14:05:08 +01:00
bruno DA SILVA
f2fbd8457d N°2498 - Authorize map extension
so as `.js.map` is not forbidden by apache
2020-02-06 14:05:08 +01:00
Molkobain
6a432c6a25 N°2757 - Fix count in group by dashlets 2020-02-06 12:12:27 +01:00
Molkobain
e96a8387a0 N°2750 - Regression: Fix default user profile image not shown in portal due to N°2060 2020-02-06 10:06:18 +01:00
Pierre Goiffon
f3576cffb0 📝 README : remove 2.4.* version as this branch isn't supported anymore by Combodo 2020-02-05 14:51:46 +01:00
Molkobain
c5625e6a8d Internal: Fix setup headers style due to 71708cf 2020-02-05 14:49:33 +01:00
Molkobain
3d2a844fef N°2313 - Markup extensibility: Improve success message display during the workflow 2020-02-05 12:10:15 +01:00
Molkobain
110a030902 PHPDoc 2020-02-05 12:10:15 +01:00
Stephen Abello
5ccd885607 Remove DB Tools from excluded modules 2020-02-05 12:04:26 +01:00
Stephen Abello
e5c6efbe69 Merge branch 'master' into develop
# Conflicts:
#	README.md
2020-02-05 11:24:44 +01:00
Stephen Abello
bd083d632f Update readme for 2.6.3 release 2020-02-05 11:22:39 +01:00
Stephen Abello
65b8132914 N°2314 - Markup extensibility: Fix collapsible icons in "About iTop" modal 2020-02-05 09:47:27 +01:00
Eric
3c2130aa72 N°2321 - Fix SQL request generation for inherited magic attributes 2020-02-04 15:54:03 +01:00
Stephen Abello
e70a2f75d3 N°2748 - Fix regression introduced by 71f5d29c, CKEditor's paragraph spacing wasn't coherent with how it's displayed in iTop 2020-02-04 11:41:10 +01:00
Molkobain
fe8e6ba4b0 N°2314 - Markup extensibility: Fix UI elements not using main colors variables 2020-02-04 10:32:57 +01:00
Eric
008614fde6 N°2321 - Fix SQL request generation for inherited magic attributes 2020-02-04 10:28:35 +01:00
Molkobain
ac6e60f5a1 N°2595 - Reorganize admin. console menus: Change new menu groups IDs to avoid collision with existing extensions 2020-02-04 09:50:11 +01:00
Stephen Abello
bf18d623d6 N°2314 - Markup extensibility: Add 2 additional themes for the backoffice
Adds a colored top bar to easily identify different environments (tests, production, ...)
2020-02-03 16:17:46 +01:00
Stephen Abello
10d04756ee N°2314 - Markup extensibility: Fall back on iTop's default theme when a non existing theme is selected 2020-02-03 15:12:59 +01:00
bruno DA SILVA
6e927114e0 N°2154 - 💚 fix tests
- the correct file is now versioned
2020-02-03 12:04:51 +01:00
Pierre Goiffon
682c24a873 N°2293 DBUpdate : save changed fields and corresponding previous values (#111)
* N°2293 DBUpdate : save changed fields and corresponding previous values for callbacks
* update PHPDoc
* remove m_aChanges and ListChangesUpdated() that were introduced in 2.7.0-beta
* add m_aPreviousValuesForUpdatedAttributes and ListPreviousValuesForUpdatedAttributes()

* :memo Woops forgot to change one PHPDoc

* 📝 Some more PHPDoc O:)

* 📝 Add more info in .doc README

* 📝 Well, again some PHPDoc O:)

* 📝 Replace inline @link by @see
@link are for URI, see https://docs.phpdoc.org/latest/references/phpdoc/inline-tags/link.html
2020-01-31 18:01:26 +01:00
bruno DA SILVA
d4b4ced649 🌐 update dictionaries
- set the translation as requested by Q&A
2020-01-31 17:29:38 +01:00
bruno DA SILVA
c2589492d9 📝 documentation generator dependencies handling improvement
- ignore /.doc/vendor
 - uses a lock file (at /.doc/composer.lock)
2020-01-31 17:29:38 +01:00
bruno DA SILVA
15c9cf926e 2154 - preserve "var" in conf
- add possibility to inject var using string patterns (ie: `'%env(DB_HOST)?:localhost%`)
 - on WriteToFile, preserve the non interpreted value when the interpreted value is kept the same
 - added unit tests for both behaviours
 - minor bugfix (default value in comment was wrong) and code readability improvements
2020-01-31 17:29:37 +01:00
Molkobain
78d4c8c7c7 Internal: Fix typo 2020-01-31 17:22:57 +01:00
Eric
d9e8eed084 💚 Fix CI on TagSet search (request have changed) 2020-01-31 16:13:59 +01:00
Eric
ebe86d09ee N°985 - Add applicable contexts on Trigger (logs) 2020-01-30 16:18:49 +01:00
Eric
5e5d368299 N°2657 - MTP : Progress Bar has disappeared (Search exact match) 2020-01-30 16:02:16 +01:00
Molkobain
f990a83453 N°2060 - Migrate error page to the Symfony framework 2020-01-30 13:56:32 +01:00
Molkobain
c6325dce8e Internal: Fix autoloader path for Symfony bin/console utility 2020-01-30 13:56:32 +01:00
Eric
bbca1625fb N°2657 - MTP : Progress Bar has disappeared (Search exact match) 2020-01-30 12:31:22 +01:00
Pierre Goiffon
53975d1d8f 📝 Replace inline @link by @see
@link are for URI, see https://docs.phpdoc.org/latest/references/phpdoc/inline-tags/link.html
2020-01-30 09:35:25 +01:00
Pierre Goiffon
1358bf9b7f 📝 Well, again some PHPDoc O:) 2020-01-30 09:25:53 +01:00
Pierre Goiffon
7c17be4db6 📝 Add more info in .doc README 2020-01-30 09:22:38 +01:00
Pierre Goiffon
367a92b711 📝 Some more PHPDoc O:) 2020-01-30 08:39:18 +01:00
Pierre Goiffon
0a3201dd41 :memo Woops forgot to change one PHPDoc 2020-01-29 18:43:46 +01:00
Pierre Goiffon
d82690dd84 N°2293 DBUpdate : save changed fields and corresponding previous values for callbacks
* update PHPDoc
* remove m_aChanges and ListChangesUpdated() that were introduced in 2.7.0-beta
* add m_aPreviousValuesForUpdatedAttributes and ListPreviousValuesForUpdatedAttributes()
2020-01-29 18:36:46 +01:00
Eric
7f9e4385ac N°2657 - MTP : Progress Bar has disappeared (support any code length) 2020-01-29 18:01:17 +01:00
Stephen Abello
aa3e284af3 Update README for 2.7.0-beta2 2020-01-29 16:59:16 +01:00
Stephen Abello
a941e5f752 Merge branch 'develop' of https://github.com/Combodo/iTop into develop 2020-01-29 11:49:38 +01:00
Stephen Abello
b1878f7265 Update version number for 2.7.0-beta2 2020-01-29 11:49:20 +01:00
Molkobain
b106a54c50 N°2314 - Markup extensibility: Add 2 additional themes for the backoffice
Basic color changes to identify different environments (tests, production, ...)
2020-01-29 11:04:20 +01:00
Molkobain
002da0b387 N°2314 - Markup extensibility: Rework some SCSS variables 2020-01-29 11:04:20 +01:00
acognet
6b9e723a45 Merge remote-tracking branch 'origin/develop' into develop 2020-01-29 09:55:44 +01:00
acognet
b54e457cbb N°2038 - New dashlet Kanban 2020-01-29 09:54:56 +01:00
jbostoen
e750dd53d8 NL translations for iTop 2.7.0 (#94)
Made by @jbostoen & @Hipska in PR #94 . Many thanks to them !
2020-01-29 09:51:11 +01:00
Eric
da6a55504e N°985 - AttributeEnumSet (portal support) 2020-01-28 17:37:17 +01:00
Eric
b58356c42e N°985 - Add applicable contexts on Trigger (search) 2020-01-28 17:37:17 +01:00
Molkobain
524e43b8c4 N°2313 - Markup extensibility: Add metadata on session messages in the end-users portal 2020-01-28 17:27:32 +01:00
Eric
a80bd6f2b9 N°985 - Add applicable contexts on Trigger (display read-only) 2020-01-28 15:29:12 +01:00
Eric
08eb9ee630 🌐 update dictionaries for 2.7.0-beta2 2020-01-28 15:29:12 +01:00
Eric
05485b838e N°985 - Add applicable contexts on Trigger (display read-only) 2020-01-28 15:29:12 +01:00
Eric
029fe6882d N°985 - Add applicable contexts on Trigger (Fix regression) 2020-01-28 15:29:12 +01:00
Eric
b31eb6aab9 Comment 2020-01-28 15:29:12 +01:00
Molkobain
a96c194676 N°2313 - Markup extensibility: Add CSS classes on object details and lists in the end-users portal 2020-01-28 15:25:12 +01:00
bruno DA SILVA
0d85331bca 1627 - Ticket ref sometimes duplicate
🐛 INSERT/UPDATE do not require to free the results
2020-01-28 11:59:04 +01:00
Pierre Goiffon
e9dec8ae05 N°330 Attachments as table : fix table sorting on size and date
* portal : use data-order attribute for the DataTable plugin (https://datatables.net/manual/data/orthogonal-data)
* console : add textExtraction override for the attachments table (https://mottie.github.io/tablesorter/docs/example-option-text-extraction.html)
2020-01-28 10:11:29 +01:00
bruno DA SILVA
6a6a0ffa24 1627 - Ticket ref sometimes duplicate
🐛 fix ref. generation when inside a transaction (by opening a new connection).
note: the portal make uses of such a transaction.
2020-01-27 17:58:30 +01:00
Molkobain
87623fba3d PHPDoc 2020-01-27 16:58:55 +01:00
Molkobain
a7619f2820 N°2313 - Markup extensibility: Add metadata on admin. console object lists 2020-01-27 16:58:55 +01:00
bruno DA SILVA
cff53d71ba N°2154 & N°2720 & N°2684 - config integrity during setup 2020-01-27 15:21:50 +01:00
Molkobain
71708cfbc7 Internal: Fix setup headers size 2020-01-27 14:54:53 +01:00
acognet
e05b3a5fb9 N°2618 - Fix missing scroll bar in DataModel Viewer for class with large number of attributs 2020-01-27 10:47:19 +01:00
acognet
bfcb1fdb30 N°2618 - Fix missing scroll bar in DataModel Viewer for class with large number of attributs 2020-01-27 10:47:19 +01:00
Eric
cc4e1ea104 N°985 - Add applicable contexts on Trigger (Add portal contexts) 2020-01-24 17:43:59 +01:00
Molkobain
5485897bbb N°2313 - Markup extensibility: Fix raw value and attribute label not always being escaped 2020-01-24 17:38:18 +01:00
Molkobain
27f343e543 N°330 - Attachments: Align table rendering to linkset tables rendering 2020-01-24 17:02:02 +01:00
Molkobain
cca79735fc N°330 - Attachments: Fix empty "Delete" column displayed all the time 2020-01-24 17:02:02 +01:00
Molkobain
c48bbfd32a N°2313 - Markup extensibility: Add metadata on attachments 2020-01-24 17:02:02 +01:00
Molkobain
3ae2058f6f N°2314 - Markup extensibility: Refactor utils::GetCSSFromSASS() to enable SCSS compilation out of a file 2020-01-24 17:02:02 +01:00
Molkobain
0a63568715 PHPDoc 2020-01-24 17:02:02 +01:00
Molkobain
f878eea68d N°330 - Attachments: Update MS Office and OpenOffice file icons with more modern versions 2020-01-24 17:02:02 +01:00
Eric
8ad2b8091c N°2657 - MTP : Progress Bar has disappeared 2020-01-23 11:28:52 +01:00
bruno DA SILVA
d6ca08efb8 N°2730 - Cannot log callstack with callback into EventIssue 2020-01-22 17:37:44 +01:00
Molkobain
ba8a2c1b15 N°2710 - Fix setup crash due to PHP notices (regression introduced in 59678955) 2020-01-22 13:32:31 +01:00
Molkobain
bd9da07734 Merge branch 'support/2.5' 2020-01-22 09:55:50 +01:00
Molkobain
3dbbf296b8 Exclude combodo-db-tools module from packages by default 2020-01-22 09:10:54 +01:00
bruno DA SILVA
0ae0336e04 N°1627 - Ticket ref sometimes duplicate
🐛 Ticket creation no more crash if the current user have limited read access
2020-01-21 18:31:09 +01:00
Molkobain
8df0ef6af9 N°2723 - Fix double scrollbar in search criterion 2020-01-21 17:40:11 +01:00
Molkobain
d77c77c03b PHPDoc 2020-01-21 17:20:12 +01:00
Molkobain
5967895561 N°2710 - Fix extremely slow page load for first user after setup (regression introduced in N°2314) 2020-01-21 17:19:16 +01:00
Molkobain
6e754d4fa5 Setup: Fix graphiz detection feedback message on Windows systems 2020-01-21 15:50:33 +01:00
Molkobain
165fd0e700 N°2314 - Setup: Improve UI of user message when CRON is running 2020-01-21 15:35:09 +01:00
Molkobain
d100ce8005 PHPDoc and code formatting 2020-01-21 15:35:09 +01:00
Eric
770f5a7b67 N°985 - Add applicable contexts on Trigger (order values in DB) 2020-01-21 14:55:03 +01:00
Molkobain
a993f6a80b PHPDoc 2020-01-21 14:19:45 +01:00
Molkobain
c8bb710d21 N°2314 - Markup extensibility: Fix regression introduced in the previous commit (used a PHP 7.x function) 2020-01-21 14:17:44 +01:00
Molkobain
d963fbd8cf N°2314 - Markup extensibility: Fix crash when no <theme> defined in datamodel 2020-01-21 12:20:59 +01:00
Eric
beda8e2810 N°985 - Add applicable contexts on Trigger (fix MTP warnings) 2020-01-21 12:01:42 +01:00
Pierre Goiffon
083f8d69c2 Revert "N°2618 DataModel viewer : fix no vertical scrollbar in MSIE"
Was introducing a regression in Chrome & Fx (cannot change tab anymore)
This reverts commit 8a666b09d6.
2020-01-21 10:51:16 +01:00
Eric
1c16eeb5e4 N°2240 - Supportability - Maintenance mode (setup reset maintenance mode) 2020-01-21 10:27:47 +01:00
Stephen Abello
ecc0b57b31 Merge branch 'master' into develop
# Conflicts:
#	css/css-variables.scss
#	css/light-grey.css
#	datamodels/2.x/authent-external/module.authent-external.php
#	datamodels/2.x/authent-ldap/module.authent-ldap.php
#	datamodels/2.x/authent-local/module.authent-local.php
#	datamodels/2.x/itop-attachments/module.attachments.php
#	datamodels/2.x/itop-backup/module.itop-backup.php
#	datamodels/2.x/itop-bridge-virtualization-storage/module.itop-bridge-virtualization-storage.php
#	datamodels/2.x/itop-change-mgmt-itil/module.itop-change-mgmt-itil.php
#	datamodels/2.x/itop-change-mgmt/module.itop-change-mgmt.php
#	datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php
#	datamodels/2.x/itop-config/module.itop-config.php
#	datamodels/2.x/itop-datacenter-mgmt/module.itop-datacenter-mgmt.php
#	datamodels/2.x/itop-endusers-devices/module.itop-endusers-devices.php
#	datamodels/2.x/itop-full-itil/module.itop-full-itil.php
#	datamodels/2.x/itop-hub-connector/module.itop-hub-connector.php
#	datamodels/2.x/itop-incident-mgmt-itil/module.itop-incident-mgmt-itil.php
#	datamodels/2.x/itop-knownerror-mgmt/module.itop-knownerror-mgmt.php
#	datamodels/2.x/itop-portal-base/module.itop-portal-base.php
#	datamodels/2.x/itop-portal/module.itop-portal.php
#	datamodels/2.x/itop-problem-mgmt/module.itop-problem-mgmt.php
#	datamodels/2.x/itop-profiles-itil/module.itop-profiles-itil.php
#	datamodels/2.x/itop-request-mgmt-itil/module.itop-request-mgmt-itil.php
#	datamodels/2.x/itop-request-mgmt/module.itop-request-mgmt.php
#	datamodels/2.x/itop-service-mgmt-provider/module.itop-service-mgmt-provider.php
#	datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php
#	datamodels/2.x/itop-sla-computation/module.itop-sla-computation.php
#	datamodels/2.x/itop-storage-mgmt/module.itop-storage-mgmt.php
#	datamodels/2.x/itop-tickets/module.itop-tickets.php
#	datamodels/2.x/itop-virtualization-mgmt/module.itop-virtualization-mgmt.php
#	datamodels/2.x/itop-welcome-itil/module.itop-welcome-itil.php
#	datamodels/2.x/version.xml
2020-01-20 16:42:42 +01:00
Stephen Abello
be9f6eff29 Merge branch 'develop' of https://github.com/Combodo/iTop into develop 2020-01-20 16:41:44 +01:00
Stephen Abello
50a8af4082 Update version number for 2.6.3 2020-01-20 16:30:51 +01:00
Stephen Abello
6a1125875b Merge branch 'support/2.5'
# Conflicts:
#	css/css-variables.scss
#	css/light-grey.css
#	datamodels/2.x/authent-external/module.authent-external.php
#	datamodels/2.x/authent-ldap/module.authent-ldap.php
#	datamodels/2.x/authent-local/module.authent-local.php
#	datamodels/2.x/itop-attachments/module.attachments.php
#	datamodels/2.x/itop-backup/module.itop-backup.php
#	datamodels/2.x/itop-bridge-virtualization-storage/module.itop-bridge-virtualization-storage.php
#	datamodels/2.x/itop-change-mgmt-itil/module.itop-change-mgmt-itil.php
#	datamodels/2.x/itop-change-mgmt/module.itop-change-mgmt.php
#	datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php
#	datamodels/2.x/itop-config/module.itop-config.php
#	datamodels/2.x/itop-datacenter-mgmt/module.itop-datacenter-mgmt.php
#	datamodels/2.x/itop-endusers-devices/module.itop-endusers-devices.php
#	datamodels/2.x/itop-full-itil/module.itop-full-itil.php
#	datamodels/2.x/itop-hub-connector/module.itop-hub-connector.php
#	datamodels/2.x/itop-incident-mgmt-itil/module.itop-incident-mgmt-itil.php
#	datamodels/2.x/itop-knownerror-mgmt/module.itop-knownerror-mgmt.php
#	datamodels/2.x/itop-portal-base/module.itop-portal-base.php
#	datamodels/2.x/itop-portal/module.itop-portal.php
#	datamodels/2.x/itop-problem-mgmt/module.itop-problem-mgmt.php
#	datamodels/2.x/itop-profiles-itil/module.itop-profiles-itil.php
#	datamodels/2.x/itop-request-mgmt-itil/module.itop-request-mgmt-itil.php
#	datamodels/2.x/itop-request-mgmt/module.itop-request-mgmt.php
#	datamodels/2.x/itop-service-mgmt-provider/module.itop-service-mgmt-provider.php
#	datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php
#	datamodels/2.x/itop-sla-computation/module.itop-sla-computation.php
#	datamodels/2.x/itop-storage-mgmt/module.itop-storage-mgmt.php
#	datamodels/2.x/itop-tickets/module.itop-tickets.php
#	datamodels/2.x/itop-virtualization-mgmt/module.itop-virtualization-mgmt.php
#	datamodels/2.x/itop-welcome-itil/module.itop-welcome-itil.php
#	datamodels/2.x/version.xml
2020-01-20 16:10:21 +01:00
Stephen Abello
878c23892d Update version number for 2.5.4 2020-01-20 15:59:08 +01:00
Eric
900e8ac6d7 N°985 - Add applicable contexts on Trigger 2020-01-20 15:50:08 +01:00
Stephen Abello
248dab9289 N°2633 - Security hardening 2020-01-20 15:46:04 +01:00
Molkobain
2fcea4d02e N°2313 - Markup extensibility: Add field label in its metadata 2020-01-20 14:43:40 +01:00
Stephen Abello
1927fc743a N°2314 - Fix left padding for menu entries 2020-01-20 11:20:56 +01:00
Molkobain
6aff09eaf7 N°2708 - Internal: Fix regression introduced with N°2313 "Undefined index 'prefix'" 2020-01-20 11:16:07 +01:00
Molkobain
54e9830a3b PHPDoc 2020-01-20 09:53:22 +01:00
Molkobain
a45819dbf0 N°2682 - Portal: Fix transaction ID not being removed 2020-01-17 17:40:37 +01:00
Molkobain
e8aaec5789 N°2060 - Regression: Fix missing PORTAL_ID constant 2020-01-17 17:33:52 +01:00
Molkobain
d16c0ffef9 N°2313 - Markup extensibility: Add metadata on admin. console object creation forms 2020-01-17 14:16:31 +01:00
Molkobain
24ad593dc8 N°2313 - Markup extensibility: Add password attributes to exclude list in metadata 2020-01-17 09:03:14 +01:00
Molkobain
0b67828ab9 Add comments in standard end-users portal XML 2020-01-17 09:03:14 +01:00
Eric
8a1a78444d N°2249 - Supportability - Updater module (unified version name) 2020-01-16 18:13:58 +01:00
Eric
4552bc0778 N°2249 - Supportability - Updater module (changed version name in the priv_module_install table) 2020-01-16 17:42:02 +01:00
Eric
3a113e31fb N°2249 - Supportability - Updater module (run setup when error occurs) 2020-01-16 17:25:28 +01:00
Pierre Goiffon
8a666b09d6 N°2618 DataModel viewer : fix no vertical scrollbar in MSIE
The scrollbar is present on the right side but after viewport limit so not visible !
Can't change div.ui-layout-pane.ui-layout-center width as it is calculated dynamically by JS Layout...
So added another container with a margin. The CSS rules are added to MSIE only using the media query tip :/
2020-01-16 16:40:36 +01:00
Pierre Goiffon
0d9dc34a08 Schema.php : replace <br/> to <br> (might break on MSIE) 2020-01-16 15:06:04 +01:00
Pierre Goiffon
c7ca1eeab5 🎨 Code formatting on pages/schema.php 2020-01-16 15:06:04 +01:00
Eric
02265135e3 N°2249 - Supportability - Updater module (run setup when error occurs) 2020-01-16 11:43:17 +01:00
Eric
2c1bf665c3 N°2249 - Supportability - Updater module (Add read-only warning) 2020-01-16 11:34:50 +01:00
Molkobain
60b6fcc783 N°2313 - Markup extensibility: Better display of success messages on form validation 2020-01-16 11:34:08 +01:00
Molkobain
53adb37f43 N°2060 - Fix session messages and SCSS compilation services being cached 2020-01-16 11:34:08 +01:00
Molkobain
a6fe564a95 Add comments in standard end-users portal XML 2020-01-16 11:34:08 +01:00
Pierre Goiffon
4945f25d49 N°2602 Portal : fix no more border around new caselog editor
With the new version of CKE N°2271, container is span.cke on MSIE instead of div.cke
2020-01-16 11:27:24 +01:00
Eric
e5f3daf88a N°2249 - Supportability - Updater module (Add read-only warning) 2020-01-16 11:19:30 +01:00
Eric
cc3e6d64e1 N°2249 - Supportability - Updater module (Allow to run setup in case of failure) 2020-01-16 10:49:49 +01:00
Eric
8024aad43d N°2249 - Supportability - Updater module (Add read-only warning) 2020-01-16 10:47:37 +01:00
Pierre Goiffon
757dbb8b25 N°2311 login page : add autofocus attribute to the id field
https://caniuse.com/#feat=autofocus
2020-01-16 10:38:08 +01:00
Eric
b370deaac9 N°2313 - Markup extensibility: Add support for both code AND title in admin. console tabs 2020-01-16 09:56:22 +01:00
Eric
026b7e1836 N°2249 - Supportability - Updater module (split ajax calls) 2020-01-15 17:09:31 +01:00
Eric
d03b924240 N°2249 - Supportability - Updater module (split ajax calls) 2020-01-15 16:58:36 +01:00
Eric
97a047e38f N°2249 - Supportability - Updater module (split ajax calls) 2020-01-15 16:42:47 +01:00
Eric
5be800cfce N°2249 - Supportability - Updater module (split ajax calls) 2020-01-15 15:48:54 +01:00
Molkobain
956b597e50 Fix how user data is retrieved for "Form Prefill" in the end-users portal 2020-01-15 15:16:50 +01:00
Molkobain
57100dee9f N°2060 - WIP: Fix cached part of the portal (sync. commit) 2020-01-15 12:53:40 +01:00
Eric
c3cc1afec1 🙈 remove unnecessary info 2020-01-15 11:37:10 +01:00
Pierre Goiffon
7fe24f58f3 N°330 Display attachments as table : portal remove author column 2020-01-15 10:26:25 +01:00
Molkobain
e4160c7cf2 N°2702 - Portal: Fix origin modal not closing when switching to editing of an object 2020-01-15 10:15:36 +01:00
Molkobain
7d87768ec4 PHPDoc and warnings suppression 2020-01-15 10:15:36 +01:00
Molkobain
9946e6c41a Fix typo 2020-01-15 10:15:36 +01:00
Stephen Abello
02b483e33e Setup's cursor style on label wasn't present in .scss file and was lost since 7b6481e 2020-01-15 10:14:59 +01:00
Stephen Abello
7b6481efbd N°2112: Setup alert message introduced by 83ba909 stopped working since 797893d🐒 🐒 2020-01-15 10:09:46 +01:00
Molkobain
efef582119 N°2306 - Security hardening (Fix regression introduced in f3b66a44, thanks to @bruno-ds !) 2020-01-15 08:42:35 +01:00
Molkobain
5056e561fe PHPDoc 2020-01-15 08:42:35 +01:00
Molkobain
4400cfde62 N°2313 - Markup extensibility: Update usages of admin. console tabs to have codes and titles 2020-01-15 08:42:35 +01:00
Molkobain
5a39581c60 N°2313 - Markup extensibility: Add support for both code AND title in admin. console tabs 2020-01-15 08:42:35 +01:00
Pierre Goiffon
4eab0e6450 N°330 Display attachments as table : portal improvements
* display attachments count in section title, updated on each add/delete
* remove "no attchments message" on adding new attachment
2020-01-14 11:56:20 +01:00
Molkobain
f3b66a44ee N°2306 - Security hardening 2020-01-14 11:50:50 +01:00
Molkobain
558f108520 N°2314 - Change breadcrumb icons color to black instead of Combodo's orange 2020-01-14 11:09:01 +01:00
Molkobain
07a93d12e2 Cleanup: Remove old/unused images/CSS files (Exhaustive list in migration notes) 2020-01-14 11:09:01 +01:00
Eric
ce127278bb N°2434 - Track field Comment in core/delete - API REST 2020-01-14 10:46:21 +01:00
Molkobain
887946144c N°2696 - Upgrade ArchiveTar to v1.4.9 (PHP 7.4 compatibility) 2020-01-14 10:35:53 +01:00
Molkobain
cc887c29fd N°2696 - Upgrade SCSSPHP to v1.0.6 (PHP 7.4 compatibility) 2020-01-14 10:35:53 +01:00
Molkobain
460836852e N°2696 - Upgrade SwiftMailer to v5.4.12 (Allow explicit tls1.0, tls1.1, tls1.2 for startTLS) 2020-01-14 10:35:53 +01:00
Eric
208d7ee7ba N°2093 - ApplyStimulus return true when stimuli is not applicable 2020-01-14 10:27:55 +01:00
Eric
3d92b73ae5 :globe-with-meridian: changed filesystem into files for itop-core-update 2020-01-14 10:14:03 +01:00
Stephen Abello
deddb0824b N°2315: Forgot password sent page needed stylization 2020-01-14 09:26:58 +01:00
Vladimir Kunin
3718899663 🌐 Update Russian translations for 2.7.0-beta 2020-01-13 17:52:15 +01:00
Pierre Goiffon
7f30d74f30 N°2269 Update Font Awesome to 5.12.0 2020-01-13 10:47:57 +01:00
Eric
21199fce34 N°2240 - Supportability - Maintenance mode exit on MTP or core update error 2020-01-10 15:32:29 +01:00
Pierre Goiffon
ad821e7d9c N°2651 rollback gitignore for lib tests dirs
Too dangerous ! We'll work properly on this but for 2.8
2020-01-10 15:23:15 +01:00
Eric
881fc2a1de N°2240 - Supportability - Maintenance mode exit on MTP or core update error 2020-01-10 14:14:20 +01:00
Vincent Dumas
44ee6baddb N°2675: Fix AdminTools DataSynchro creation
User with a profile enabling write access on the group id="AdminTools" was not able to create a DataSynchro also it should have been.
2020-01-09 16:07:47 +01:00
Federico Lazcano
42d782740e Paste error! 2020-01-09 10:05:22 +01:00
Federico Lazcano
cf16229948 🌐 Typos and new translations in ES CR 2020-01-09 10:05:22 +01:00
Federico Lazcano
a25427f4c6 Change from alias to real name
Sorry @Molkobain i changed my mind :-|
2020-01-09 10:01:03 +01:00
Federico Lazcano
04b2f7c836 🌐 Added ES CR translations 2020-01-09 09:28:36 +01:00
Molkobain
0f917af55a 👥 Add @lazki to our contributors list. Thanks! 👏 2020-01-09 09:12:27 +01:00
Federico Lazcano
daaed4696e 🌐 Typo in ES CR Translation 2020-01-09 09:08:34 +01:00
Pierre Goiffon
5f52c273d9 N°2329 PHP 7.4 compat : update setup requirements
This PHP version should be ok : we will give it a try during the beta program
2020-01-09 09:05:24 +01:00
Molkobain
b8d35e4783 👥 Add Guy Couronné (@GurneyHallack) to our contributors list! 2020-01-08 19:53:12 +01:00
Molkobain
149dff4b4d N°2313 - Markup extensibility: Add markup hooks on BrowseBrick and ManageBrick tables 2020-01-08 19:48:45 +01:00
Molkobain
f235e0cd66 Update copyright 2020-01-08 19:48:45 +01:00
Pierre Goiffon
e47e02932a N°2329 PHP 7.4 compat : fix warnings in TCPDF
Integrate last TCPDF fork version (6.3.2)
2020-01-08 17:50:51 +01:00
Eric
4544bba652 N°2240 - Supportability - Maintenance mode (setup CRON message) 2020-01-08 17:29:53 +01:00
Eric
9445e12254 MSIE 11 minimum 2020-01-08 17:23:36 +01:00
Eric
951945a607 N°2240 - Supportability - Maintenance mode (Better REST/Export message) 2020-01-08 17:17:34 +01:00
Eric
34f8fff01c Fetch() ignore row when sub-class does not exist 2020-01-08 16:38:11 +01:00
Stephen Abello
8d45e48ce1 🥅 N°1192 Portal: Increase navigation rules checks robustness 2020-01-08 15:31:19 +01:00
Eric
ed26f1cecc Fix menu creation flags 2020-01-08 15:10:10 +01:00
Molkobain
bc298afda3 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	datamodels/2.x/combodo-db-tools/cs.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/da.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/datamodel.combodo-db-tools.xml
#	datamodels/2.x/combodo-db-tools/db_analyzer.class.inc.php
#	datamodels/2.x/combodo-db-tools/dbtools.php
#	datamodels/2.x/combodo-db-tools/de.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/default.css
#	datamodels/2.x/combodo-db-tools/default.scss
#	datamodels/2.x/combodo-db-tools/en.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/es_cr.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/fr.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/hu.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/it.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/ja.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/nl.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/pt_br.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/ru.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/tr.dict.combodo-db-tools.php
#	datamodels/2.x/combodo-db-tools/zh_cn.dict.combodo-db-tools.php
2020-01-08 12:03:55 +01:00
Molkobain
4f0e3430c0 Merge remote-tracking branch 'origin/support/2.5' 2020-01-08 11:58:15 +01:00
Molkobain
3347f400b8 Internal: Revert files deleted by mistake 🙈 2020-01-08 11:57:29 +01:00
Molkobain
6082308e20 Add combodo-db-tools/1.0.7 module as a default module 2020-01-08 11:40:35 +01:00
odain
1fc290587c N°2154: Fix security breach (scratch install usecase) 2020-01-08 10:10:49 +01:00
Molkobain
77fa02fcf9 PHPDoc and warnings suppression 2020-01-08 10:06:03 +01:00
Molkobain
d445551031 N°1986 - Fix regression introduced in b91183e9, creation form should never be read-only 2020-01-08 10:05:33 +01:00
Molkobain
ebfe9da464 N°2306 - Security hardening 2020-01-07 20:59:09 +01:00
odain
fdd79e91f0 N°2154: Fix security breach (scratch install usecase) 2020-01-07 17:44:39 +01:00
Molkobain
94a09493b0 N°2306 - Security hardening (BC break for some portal extensions, see migration notes) 2020-01-07 17:40:03 +01:00
Molkobain
56dbbb09dc Fix dependencies between modules 2020-01-07 17:00:19 +01:00
Pierre Goiffon
f019e05af5 N°2042 deprecated chrono extensivity 2020-01-07 16:07:25 +01:00
bruno DA SILVA
838c4f123c 👌 peer review
mostly coding convention,

thanks @molkobain
2020-01-07 15:34:27 +01:00
bruno DA SILVA
2043010aad N°2293 - API OnDBUpdate and AfterUpdate need modified fields and previous data
- add a getter for the protected property DBObject::$m_aChanges
2020-01-07 15:05:29 +01:00
bruno DA SILVA
cda18b950e N°524 - Password policy
- removal of a forgotten console.debug
2020-01-07 14:28:20 +01:00
Molkobain
acf28ca4aa N°2306 - Security hardening 2020-01-07 13:54:16 +01:00
Eric
864ded2102 Refactor Core Update (+8 squashed commit)
Squashed commit:

[b907bb759] Refactor Core Update

[5da2473aa] Refactor Core Update

[3fce45615] Refactor Core Update

[5f050a828] Refactor Core Update

[4b9b85174] Refactor Core Update

[f637ed358] Refactor Core Update

[56543edce] Refactor Core Update

[7f06900ef] Refactor Core Update
2020-01-07 10:00:14 +01:00
Eric
5cdc58846b Allow browsing developed OQL class tree 2020-01-07 10:00:14 +01:00
Eric
45cd96eb0d More informative message when class does not exist 2020-01-07 10:00:14 +01:00
Eric
c8335499fd Allow browsing developed OQL class tree 2020-01-07 10:00:14 +01:00
Eric
7f3efe59ab Refactor Core Update (+3 squashed commit)
Squashed commit:

[e1cbfe93f] Refactor Core Update

[41ec2adf7] Refactor Core Update

[ca6cefca3] Refactor Core Update
2020-01-07 10:00:13 +01:00
bruno DA SILVA
69551378c2 n°524 - password policy
- ajax message is now translated in the user's language
 - prevent the form submission if the password policy is not respected
2020-01-06 18:33:47 +01:00
odain
f3fd4bde87 💚 2020-01-06 16:09:41 +01:00
bruno DA SILVA
c115f64cb5 N°2154 - Security breach 2020-01-06 15:31:31 +01:00
Stephen Abello
ee61c1e8fb N°524: Fix style for inputs' feedback on "change password" page 2020-01-06 13:51:40 +01:00
bruno DA SILVA
e716fb118b N°2574 - enable Password expiry 2020-01-06 12:14:34 +01:00
bruno DA SILVA
b0c76346a5 N° 524 - password policy
better colors for : "change pwd" page: add feedback during the password typing
2020-01-06 11:45:48 +01:00
Pierre Goiffon
56807fd941 Person sample file : fix Descartes email
Fix typo introduced in 906e309791
2020-01-06 11:17:57 +01:00
Molkobain
b56f248b79 👥 Add Pimkie to our contributors list (BR / IT translations) 2020-01-06 09:50:15 +01:00
bruno DA SILVA
7a85201a07 524 - password policy
"change pwd" page: add feedback during the password typing
2020-01-06 09:31:28 +01:00
Purple Grape
97ebffd5fb improved chinese translations
1 improved chinese translations
2 fix some missing enries
3 correct line number against english language
2020-01-06 08:43:25 +01:00
Eric
46c239c211 typo 2020-01-03 16:40:49 +01:00
Vincent Dumas
906e309791 Replace Erri De Luca by René Descartes 2020-01-03 11:10:44 +01:00
bruno DA SILVA
4ad1ca0fc6 add option classmap-authoritative to composer.json 2020-01-02 14:19:00 +01:00
bruno DA SILVA
015955f396 N°2306 - Security hardening 2019-12-30 17:31:50 +01:00
Pierre Goiffon
9bee1905c8 N°2329 PHP 7.4 compat : remove get_magic_quotes_gpc/get_magic_quotes_runtime calls
Methods are now deprecated and since PHP 5.4 were always returning false
2019-12-24 17:30:12 +01:00
Pierre Goiffon
8ab157eae4 N°2329 PHP 7.4 compat, AttributeDefinition : fix visibilities of members called by AttributeDefinition::__construct
Not enough to get rid of child classes constructors though :/
2019-12-24 15:28:36 +01:00
Molkobain
17978b829b Internal: Simplify classes FQN 2019-12-24 15:08:45 +01:00
Molkobain
4ddb23cd7c N°2654 - Portal: Fix filter on external key when coming from filter brick 2019-12-24 15:08:45 +01:00
Pierre Goiffon
e27eb7419e N°2329 PHP 7.4 compat, AttributeDefinition : add __construct() to child classes
Shouldn't be necessary but if not present PHP 7.4.0/7.4.1 is crashing when executing new Attribute...(...)
2019-12-24 12:22:49 +01:00
Pierre Goiffon
64ef572429 🎨 AttributeDefinition : some little inspections fixes, and fix misordered function modifiers
According to PSR, we should use public static and not static public
See https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards
2019-12-24 12:22:49 +01:00
Pierre Goiffon
2f81e0fd6f N°2329 PHP 7.4 compat : remove deprecated array with curly braces 2019-12-23 18:44:52 +01:00
odain
987f1f7dbf N°2568 - Log_KPI viewer in the console - Fix broken links 2019-12-23 17:17:10 +01:00
Pierre Goiffon
b53c91f7f3 Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	Jenkinsfile
#	setup/setuputils.class.inc.php
2019-12-23 11:34:55 +01:00
Pierre Goiffon
5fce2a2c1c Setup : fix MySQL TLS wiki URL 2019-12-23 11:27:56 +01:00
Pierre Goiffon
13d31ac211 utils::GetDefaultUrlAppRoot : make comparison case insentitive, add a test 2019-12-23 11:06:29 +01:00
Pierre Goiffon
57ae29cf2f \SetupPage::error : remove "error" title as existing calls already add it 2019-12-23 09:54:07 +01:00
Pierre Goiffon
0b3895e39e Core update : fix CanUpdateCore result never displayed on server with warning enabled
Was caused by a call to :
\Combodo\iTop\FilesInformation\Service\FilesInformationUtils::Scan
With path set to ''. Made from :
\Combodo\iTop\FilesInformation\Service\FilesInformation::CanUpdateCore
2019-12-20 18:37:51 +01:00
Pierre Goiffon
0d231d9b94 N°2651 Remove lib test files from index 2019-12-20 17:07:20 +01:00
Molkobain
27a6abeeb3 Internal: Add Benjamin to the sample data to welcome him! 2019-12-20 12:21:02 +01:00
Pierre Goiffon
c75e6960a7 N°2651 Remove lib test files from index 2019-12-20 11:57:18 +01:00
Pierre Goiffon
4766ca3fd0 N°2650 fix run_query error handling incompatible with PHP < 7.3.0 2019-12-20 09:21:00 +01:00
odain
523dd97eca N°2570 Update iTop license list 2019-12-19 16:40:17 +01:00
bruno DA SILVA
c09bd2bfc6 2626 - log modularity: filterable logs using minimal log level per channel
🔊 Adding log level Trace, which is not logged by default (as for Debug)
2019-12-19 11:17:18 +01:00
Molkobain
bd662eaf19 Update README to add 2.7.0-beta section 2019-12-18 16:47:11 +01:00
Pierre Goiffon
ff75ecfe27 💄 Setup : add styling on prerequisites summary title 2019-12-18 11:12:21 +01:00
Pierre Goiffon
54f8b74383 📝 restore WizardStep documentation
Do not add documentation in a doc block containing @copyright O:)
2019-12-18 11:05:41 +01:00
Pierre Goiffon
18a506673f 💄 Setup : restore label{ cursor:pointer;} 2019-12-18 10:44:28 +01:00
Pierre Goiffon
a108be8517 N°330 Fix first column not centered when creating attachment on new ticket 2019-12-18 09:56:33 +01:00
Eric
3e57c3b1e9 N°2249 - Supportability - Updater module (application upgrade) 2019-12-18 09:45:34 +01:00
Stephen Abello
f18e27a183 N°2314 Setup progress bar is now cuter (following 01cb88a) 2019-12-18 09:40:48 +01:00
Pierre Goiffon
358efb0f2f N°2518 Change log level for non existent legacy log file when trying to rename 2019-12-18 09:28:11 +01:00
bruno DA SILVA
723fc917f1 🐛 change back to an authoritative autoloader 2019-12-18 09:25:35 +01:00
Pierre Goiffon
01f34eea29 N°330 Attachments display as table : portal. Table is now responsive, attachment section is collapsable (collapsed by default) 2019-12-17 18:14:21 +01:00
bruno DA SILVA
705d941979 ⬆️ Upgrading dependencies 2019-12-17 17:43:18 +01:00
Stephen Abello
691acb45e6 N°2314 2 css variables were not overridable 2019-12-17 15:23:02 +01:00
Pierre Goiffon
97e4ff30ff N°330 attachments size column : right align (both on console and portal)
Allows easier comparison to see bigger files
2019-12-17 15:12:28 +01:00
Pierre Goiffon
34c76c735a N°330 Attachments display as table : portal. Various improvements
* update CSS for table layout (vertical align, change text-align)
* tooltip : fix max size was not used anymore (tooltip was removing the style attributes)
* tooltip : fix tooltip always available
* delete button background color set to same as validate button
* remove "type" column
* reduce attachment line height
2019-12-17 15:08:22 +01:00
Pierre Goiffon
47d8e35639 N°330 Improve \ormDocument::GetFormattedSize
* Fix typo in method name (many thanks @jbostoen !)
* Use \utils::BytesToFriendlyFormat
2019-12-17 15:08:22 +01:00
odain
d4dc739b30 Finalize license update file 2019-12-17 14:56:13 +01:00
acognet
b84859b07e optimizing the display of the label in FieldsExpression 2019-12-17 13:07:21 +01:00
bruno DA SILVA
3f154fa765 2626 - log modularity: filterable logs using minimal log level per channel
🐛 logs are no more written twice.
2019-12-17 12:33:34 +01:00
Eric
f31e32d7a9 N°2249 - Supportability - Updater module (disable submit when no file provided) 2019-12-17 11:53:29 +01:00
Molkobain
1589535a45 Internal: Add automatic refresh of the maintenance page every 30s to redirect user to iTop when done 2019-12-17 11:48:27 +01:00
odain
53353ec9e1 Merge branch 'develop' of https://github.com/Combodo/iTop into develop 2019-12-17 11:44:17 +01:00
odain
a601b1c59a release: css part of community-licenses.xml 2019-12-17 11:44:13 +01:00
odain
e093abcb2b release: lib part of community-licenses.xml 2019-12-17 11:28:05 +01:00
Molkobain
01cb88a661 N°2314 Regression: Add progress bar color change while on going to show user it's not stucked 2019-12-17 11:15:00 +01:00
Molkobain
5ef32b6b31 N°2314 Regression: fix invisible icons due to wrong FontAwesome calls 2019-12-17 11:09:10 +01:00
Stephen Abello
ba1c719568 N°2314 Regression: fix invisible icons due to wrong FontAwesome calls 2019-12-17 10:55:05 +01:00
Eric
0507e4f4a8 🔖 version 2.7.0-beta 2019-12-17 10:35:15 +01:00
Eric
60eb312e68 🔖 version 2.7.0-beta 2019-12-17 10:28:13 +01:00
Eric
044a8926b3 🔖 version 2.7.0-beta 2019-12-17 10:25:28 +01:00
odain
acf8c9d49e release: js part of community-licenses.xml 2019-12-17 10:22:07 +01:00
Eric
d99e79eb3f 🔖 xml version 1.7 2019-12-17 10:22:03 +01:00
Eric
843c8ccd38 🔖 version 2.7.0 2019-12-17 10:18:15 +01:00
Eric
d45326606d 🌐 Update translations 2019-12-17 10:09:10 +01:00
Molkobain
5886d038e3 Regression: Fix iTop Hub connector not being installed (caused by dd5ac38dd4) 2019-12-17 09:50:27 +01:00
Pierre Goiffon
8fed7c7005 💄 Setup : TLS message style was hardcoded, get back to standard CSS classes to be aligned with the rest 2019-12-17 09:16:10 +01:00
Molkobain
604522aa61 N°2314 Regression: Work on Setup stylesheet 2019-12-16 18:07:59 +01:00
Molkobain
7af35c2d09 Internal: Improve how CSS and JS files are loaded in the DOM to optimize rendering and avoid glitches 2019-12-16 17:53:43 +01:00
Stephen Abello
87497eb491 N°2314 Regression: extracted setup's stylesheet in its own file and pimped it up 2019-12-16 17:05:36 +01:00
Pierre Goiffon
434ed0dd4e N°330 Attachments display as table : portal 2019-12-16 15:51:56 +01:00
Pierre Goiffon
473a55bde6 🎨 bsfileuploader : code formatting 2019-12-16 15:25:32 +01:00
Pierre Goiffon
0b65b36e74 N°330 Attachments : add attributes in Attachment object
* creation date
* external key to creator User
2019-12-16 15:25:32 +01:00
Pierre Goiffon
4aeb78ccac N°330 Attachments display as table : console 2019-12-16 15:25:31 +01:00
Pierre Goiffon
21d5de1756 Fix run_query throwing exception when inccorect query and no suggestions returned 2019-12-16 15:07:26 +01:00
Stephen Abello
797893d317 N°2314 Regression: fix crash on setup ⌨️ 🐒 2019-12-16 14:16:29 +01:00
Eric
dd5ac38dd4 N°2249 - Supportability - Updater module (application upgrade) 2019-12-13 17:28:35 +01:00
Eric
2741a446ea Integrate database integrity module 2019-12-13 17:28:08 +01:00
Molkobain
da3d886bd7 N°2618 - Fix missing scroll bar in DataModel Viewer for class with large number of attributs 2019-12-12 16:41:09 +01:00
Stephen Abello
ad40e02fee N°2314 Forgot to commit these changes with 611e82 🙈 2019-12-12 15:55:06 +01:00
Vincent Dumas
7cc63e21dd Prevent trigger without friendlyname
Make 'description' mandatory as it is used as friendly name, and a blank value is not clickable in the UI
2019-12-12 15:47:39 +01:00
Stephen Abello
611e828d1a N°2314 Introduce custom themes for iTop's console 2019-12-12 15:46:03 +01:00
odain
3abcd59b03 remove redundant test on log api 2019-12-12 14:41:26 +01:00
odain-cbd
48f4cd8943 Merge pull request #101 from Combodo/feature/faf_log_modularity
Feature/faf log modularity
2019-12-12 14:44:02 +01:00
odain
abd5e27c2e fix and complete testcase 2019-12-12 14:22:49 +01:00
Eric
ccb3a21c68 N°2595 - Reorganize/rename admin. console menus (hide empty sub-menus) 2019-12-12 10:20:29 +01:00
Molkobain
aa060746d7 N°2009 - Fix non editable dashboard when wrong attribute code used in its definition 2019-12-12 09:50:13 +01:00
odain
6ec441e501 peer review: added tests but not passed yet 2019-12-12 08:43:01 +01:00
Molkobain
52d3d8cfe7 Internal: PHPDoc, warnings suppression and typos 2019-12-11 20:54:45 +01:00
Molkobain
081ba68af4 N°2634 - Fix non editable dashlets in dashboards 2019-12-11 20:54:45 +01:00
odain
38d5889979 log kpi: class name added to next page buttons in order to use them in itop-log-mgt 2019-12-11 17:58:49 +01:00
Molkobain
5b4808c378 Internal: PHPDoc 2019-12-11 15:58:47 +01:00
Molkobain
2af22d3387 N°2060 Fix regression introduced during migration (iPortalUIExtension extensions not working) 2019-12-11 15:54:01 +01:00
Eric
7c1a8c90da N°2595 - Reorganize/rename admin. console menus 2019-12-11 13:59:31 +01:00
Molkobain
e7726a17db N°2630 - Portal: Fix wrong "apply stimulus" form being used in a branch of classes 2019-12-10 21:51:28 +01:00
Molkobain
57da9a848d Internal: Optimize regexp to remove warning 2019-12-10 21:51:28 +01:00
Molkobain
464ee46631 Internal: Refactor a small piece of code for better readability 2019-12-10 21:51:28 +01:00
Molkobain
46358b6e36 N°1982 - Fix resolution date not updated as expected when concurrent access from both portal and admin. console 2019-12-10 21:51:28 +01:00
Pierre Goiffon
46d6779562 🎨 utils : some code formatting + 1 @var 2019-12-10 17:57:55 +01:00
bruno DA SILVA
ea708e1e05 995 - Searching in OQL, URs with a particular request template field value
added AllowDelete in order to bypass the user's rights
2019-12-10 16:59:44 +01:00
Eric
374946505a N°2381 - Not possible to create a ticket in "resolved" with lnk objects 2019-12-10 15:30:53 +01:00
Pierre Goiffon
18db31f138 Log : rename config parameter from 'min_log_level' to 'log_level_min' 2019-12-10 09:46:42 +01:00
Pierre Goiffon
332c6eb33c log : factorize level & context 2019-12-10 09:39:46 +01:00
bruno DA SILVA
e5c49e3bd4 filterable logs using min_log_level optionnaly per channels 2019-12-10 09:03:14 +01:00
Molkobain
60769dc4b7 Internal: Add Dimitri to the sample data to welcome them! 2019-12-09 16:15:56 +01:00
Pierre Goiffon
9e652ef214 Update iPortalUIExtension param (not using Silex anymore but Symfony) 2019-12-09 16:11:29 +01:00
Pierre Goiffon
d2825c1b24 iTop Hub Connector : remove installation.xml
Was commited by mistake (duplicate of datamodels/2.x/installation.xml)
2019-12-09 16:02:39 +01:00
Eric
8482be7068 N°2623 - Fix setup request error 2019-12-09 14:56:42 +01:00
Eric
9dc56b727e N°1987 - Fix search equals 0 for integer fails 2019-12-06 17:01:46 +01:00
Eric
c6759220b9 🎨 clean warnings and add KPI in ajax.render.php 2019-12-06 15:42:03 +01:00
Pierre Goiffon
470af54acb Add some HTML in errors thrown by \MetaModel::DBCheckFormat 2019-12-05 17:56:42 +01:00
Eric
344f74f444 N°1213 - Allow NOT IN SELECT in OQL syntax - Fix UNION queries 2019-12-05 17:33:47 +01:00
Molkobain
0d1ca1bc0e N°2611 - Make itop-portal-base module mandatory during setup 2019-12-05 17:30:30 +01:00
Molkobain
d9e3684d3d Internal: Add Marie-Annette to the sample data to welcome them! 👋 2019-12-05 17:07:05 +01:00
Molkobain
e59db3f3d9 Internal: Add Alexandre, Anne-Catherine and Olivier to the sample data to welcome them! 👋 2019-12-05 15:47:46 +01:00
Molkobain
c883d618c3 N°1192 - Change default behavior for navigation rules 2019-12-05 15:02:36 +01:00
Eric
8911a9a3ed N°2381 - Not possible to create a ticket in "resolved" with lnk objects 2019-12-05 13:06:23 +01:00
Molkobain
366d2754ef N°2060 - Fix regression making url pointing to the portal not working in notification anymore 2019-12-05 12:39:28 +01:00
Molkobain
4db114f64d N°2313 - Markup extensibility: Add meta information and hooks
Admin. console:
- Class and id on object form
- Class current and target state on object transition form
- Object form mode (view/edit/create/stimulus)
- Object attributes: Attribute code, attribute definition class and raw value
- Navigation menu: Their IDs on group and items
- Actions menu: Their IDs on items

Portal:
- Object attributes: Attribute code, attribute definition class and raw value
2019-12-05 12:27:02 +01:00
Pierre Goiffon
d36340a3cd TagSet tests : use expectException method 2019-12-05 11:50:58 +01:00
Pierre Goiffon
7e6de5d8dd 🐛 \SetupUtils::builddir : avoid infinite loops 2019-12-05 10:27:22 +01:00
bruno DA SILVA
490eda4f4a n°524 - password policy
bugfix : Enter Password phrase no more lack the userName and is properly displayed
2019-12-05 09:55:44 +01:00
Eric
b5b4d70c2d N°1213 - Allow NOT IN SELECT in OQL syntax - Add unit tests 2019-12-05 09:02:11 +01:00
Eric
a74cff6902 N°1213 - Allow NOT IN SELECT in OQL syntax - Fix unit tests 2019-12-04 15:39:44 +01:00
Eric
1010274c48 N°1213 - Allow NOT IN SELECT in OQL syntax - Fix search init & code cleanup 2019-12-04 14:50:36 +01:00
Eric
c39ff13217 N°1213 - Allow NOT IN SELECT in OQL syntax - Fix search init 2019-12-04 14:40:53 +01:00
Eric
58da108e85 N°1213 - Allow NOT IN SELECT in OQL syntax 2019-12-04 14:27:02 +01:00
Eric
8b4fdb54ea N°1213 - Allow NOT IN SELECT in OQL syntax - support of UNION queries 2019-12-04 11:17:51 +01:00
Pierre Goiffon
1877cb5e93 N°2614 Fix datasynchro error when running synchro_import.php 2019-12-04 11:08:18 +01:00
bruno DA SILVA
1530b3f2ca 🐛 apc_clear_cache & opcache_reset can both be called
especially since not calling apc_clear_cache may result in invalid apcu entries...
2019-12-04 10:39:30 +01:00
Eric
4c3c5228aa N°1213 - Allow NOT IN SELECT in OQL syntax 2019-12-03 17:23:14 +01:00
Eric
3ae4ca89f4 N°1213 - Allow NOT IN SELECT in OQL syntax - support of UNION queries and "nested nested" queries 2019-12-03 17:14:53 +01:00
Eric
b415b1eeae N°1213 - Allow NOT IN SELECT in OQL syntax - support of UNION requests 2019-12-03 12:11:00 +01:00
Eric
c0ae983faa N°1213 - Allow NOT IN SELECT in OQL syntax 2019-12-03 12:11:00 +01:00
acognet
7845cbcc55 update tests 2019-12-03 12:11:00 +01:00
acognet
a33977251e N°1213 - Allow NOT IN SELECT in OQL syntax 2019-12-03 12:11:00 +01:00
odain
b0d668b124 N°1213 - Allow NOT IN SELECT in OQL syntax - add unit tests 2019-12-03 12:11:00 +01:00
odain
b0856c1abf fix HTMLDOMSanitizerTest due to merge 2019-12-03 12:10:26 +01:00
Pierre Goiffon
2bffa3cf17 N°2615 PHP versions setup requirements/warnings 2019-12-03 11:54:13 +01:00
odain
5ab05c6a6a Merge branch 'develop' of https://github.com/Combodo/iTop into develop 2019-12-03 11:42:08 +01:00
odain
72af2b7cd6 comment OQL section in phpunit.dist.xml 2019-12-03 11:41:57 +01:00
Molkobain
143c0a5b07 N°2616 - Fix hyperlink placeholder not working in notifications for other portals 2019-12-03 11:39:14 +01:00
Molkobain
daf79f2324 N°2272 Fix calls to DBObjectSet::OptimizeColumnLoad() 2019-12-03 11:39:14 +01:00
odain
15d11c6c86 reomve echo that prevented phpunit execution 2019-12-03 11:26:35 +01:00
odain
b298a1fa82 fix invalid merge 2019-12-03 11:23:25 +01:00
odain
007b8147c7 fix phpunit step 2019-12-03 11:17:01 +01:00
odain
4013b76c9e Merge branch 'speedup_jenkins' into develop 2019-12-03 11:09:30 +01:00
odain
ed81391aff !y# This is a combination of 4 commits.
run OQL tests depending on jenkins param

try sth

try sth

try sth

try sth

try sth

fix parameter to run OQL tests or not

run OQL tests depending on jenkins param

try sth

try sth

try sth

try sth

try sth

fix parameter to run OQL tests or not
2019-12-03 11:02:15 +01:00
Pierre Goiffon
28818010d6 N°2558 Test for the HTMLDOMSanitizer white list 2019-12-03 10:58:21 +01:00
Eric
928c19f923 move OQL2SQL tests to another folder 2019-12-03 10:58:21 +01:00
Eric
34aa240840 🎨 Fix bad parameter init 2019-12-03 10:58:21 +01:00
Eric
6872dbd180 🎨 Fix bad parameter init 2019-12-03 10:58:21 +01:00
Pierre Goiffon
c5bea30c64 N°2329 PHP 7.4 compat : remove deprecated get_magic_quotes_gpc() function call 2019-12-03 10:58:21 +01:00
Molkobain
071a254611 N°2311 Fix focus set to sumbit button when pressing "tab" key after filing inputs 2019-12-03 10:58:21 +01:00
Molkobain
d2d203df34 💄 Increase blur effect on portal modal backdrop 2019-12-03 10:58:21 +01:00
Molkobain
e1ffdea172 N°2272 Fix call to DBObjectSet::OptimizeColumnLoad() in the portal (doesn't support "id" attribute anymore) 2019-12-03 10:08:46 +01:00
Pierre Goiffon
9abcf40df7 N°2558 Test for the HTMLDOMSanitizer white list : remove new lines
the parser gives different results depending on the PHP version
Didn't manage to get it right :
- no php.ini difference
- playing with the parser preserveWhitespace/formatOutput parser options didn't help

So we're removing new lines on both sides :/
2019-12-03 09:47:45 +01:00
Pierre Goiffon
b67dc888fe N°2558 Test for the HTMLDOMSanitizer white list 2019-12-03 08:42:25 +01:00
Eric
c7b101d169 move OQL2SQL tests to another folder 2019-12-03 08:38:57 +01:00
Eric
df5a7440a4 🎨 Fix bad parameter init 2019-12-03 08:38:57 +01:00
Eric
a59a8c0ec5 🎨 Fix bad parameter init 2019-12-03 08:38:57 +01:00
Pierre Goiffon
bf9f43da8b N°2329 PHP 7.4 compat : remove deprecated get_magic_quotes_gpc() function call 2019-12-02 13:47:44 +01:00
Molkobain
928b82a9c8 N°2311 Fix focus set to sumbit button when pressing "tab" key after filing inputs 2019-11-29 17:40:07 +01:00
Molkobain
b9008d2459 💄 Increase blur effect on portal modal backdrop 2019-11-29 17:38:20 +01:00
odain
50dddaaa9b add debug mode as param + use params in phpunit.sh 2019-11-29 16:46:59 +01:00
odain
1b168501af add a parameter to Jenkins pipeline 2019-11-29 16:37:01 +01:00
bruno DA SILVA
35a49bad60 n°524 - password policy
typos

(thanks @jbostoen)
2019-11-29 08:56:18 +01:00
Pierre Goiffon
97c1ff55e9 📝 Comment to explain why Uniqueness isn't protected by a lock 2019-11-28 17:16:01 +01:00
Molkobain
ffead92d5a N°2613 - Portal: Fix crash in object form having empty AttributeBlob field 2019-11-28 17:05:05 +01:00
Molkobain
81ea2b1fe4 Internal: Add comment on suspected dead code 2019-11-28 17:05:05 +01:00
Molkobain
9cdfe0ecb4 Internal: PHPDoc and warning suppression 2019-11-28 17:05:05 +01:00
bruno DA SILVA
0473269132 n°1617 - iTop Fence
- iTop Fence now lock user fetch/creation per login to prevent duplicates
 - iTop now provide a mean to create safe lock when used untrusted strings
 - better log message & function names
 - reduced cyclomatic complexity

(thanks @piRGoif)
2019-11-28 17:03:31 +01:00
Eric
4798bf2f79 N°2408 - Fix FromOQL() with NULL 2019-11-28 11:54:42 +01:00
bruno DA SILVA
c3c5c56dd8 n°524 - password policy
- changed translation key to be more conventional
2019-11-28 11:40:28 +01:00
bruno DA SILVA
4fb9bbb831 n°524 - password policy
- "change password" is now handled gracefully by the portal
2019-11-28 09:05:20 +01:00
bruno DA SILVA
267cdd2aee n°524 - password policy
- "password reset" workflow now handle gracefully the policy enforcement.
2019-11-28 08:52:02 +01:00
bruno DA SILVA
fe0bd1a4b8 n°524 - password policy
- translations added
 - fields are now visible in the detail view
 - minor translation modification for the User object
2019-11-28 08:52:02 +01:00
Molkobain
9fa510d2a8 Internal: PHPDoc and warning suppression 2019-11-27 20:40:39 +01:00
Molkobain
fd29986354 N°2000 - Fix blank page when displaying a synchronized object 2019-11-27 20:07:17 +01:00
Molkobain
8ec6bb4758 N°1666 - Fix missing scroll bar missing in modal window "Create a new field" from Request Template 2019-11-27 19:35:19 +01:00
Molkobain
f3306f5fb4 N°1616 - Fix truncated caselog entry with large HTML table or word 2019-11-27 19:05:37 +01:00
Molkobain
20683fdf50 N°1192 Fix transition form always redirecting to object no matter the navigation rule 2019-11-27 17:21:01 +01:00
Molkobain
603ae8c0e1 N°2272 Fix calls to DBObjectSet::OptimizeColumnLoad() in the portal (now requires ClassAlias to always be specified) 2019-11-27 17:21:01 +01:00
Eric
f1d0418e48 🐛 Fix error when no cache is configured 2019-11-27 16:02:46 +01:00
Eric
9c8c306df3 🐛 Fix DBSearch::Intersect (de-duplicate aliases) 2019-11-27 15:39:33 +01:00
Eric
d2543e9c67 🐛 Fix bad calls to OptimizeColumnLoad 2019-11-27 15:38:30 +01:00
Pierre Goiffon
55a0d910fa N°2490 MariaDB compat : changes after code review
* DEFAULT value unquoting is done with preg_replace now (clearer that we want to do a string replacement)
* DEFAULT value unquoting works even if DEFAULT is not the last keyword
* change test datasource for more readability (use double quotes when needed)
2019-11-27 11:51:47 +01:00
Pierre Goiffon
bd8144a67c 👷 Jenkins : config file is now picked by the unattended install
The problem was that we had an absolute path in the XML : changing it to the file name is sufficient ! The copy introduced in 443763de was unecessary.
2019-11-27 11:45:30 +01:00
Pierre Goiffon
004d1a7245 🎨 Rename constant 2019-11-27 10:15:24 +01:00
Pierre Goiffon
09fb99ed58 Rollback 2f431a0d : might not be a typo, we don't want to cause trouble close to a beta version O:) 2019-11-26 17:31:41 +01:00
Eric
ae8071f707 add unit tests for intersect 2019-11-26 17:24:23 +01:00
Eric
c1cf084e43 fix unit tests - Support Microsoft encoding of non breaking line in UTF-8 2019-11-26 16:32:19 +01:00
Eric
87c794b22e fix unit tests - Support Microsoft encoding of non breaking line in UTF-8 2019-11-26 15:44:17 +01:00
Molkobain
a382d6ad35 N°1192 Change "close rule" behaviour to redirect to homepage if browser doesn't let us close the window 2019-11-26 14:57:35 +01:00
Eric
2d86599a19 N°2519 - ev_timeout from "New" to "Escalated TTO" doesn't work 2019-11-26 13:55:26 +01:00
Pierre Goiffon
914971b30d 💚 Jenkins : Fix default config database name 2019-11-26 12:15:02 +01:00
Pierre Goiffon
443763de48 💚 Jenkins : copy default config after unattended install
The generated config file had too many default values, and not the wanted ones
2019-11-26 12:09:03 +01:00
Stephen Abello
ff3c7ebe54 N°971 Portal: Fix Browse brick n:n level links in tree and mosaic display 2019-11-26 11:39:25 +01:00
Stephen Abello
c0f82f25a3 N°956 Portal: Increase robustness 💪 2019-11-26 11:39:25 +01:00
Eric
f90381d412 Support Microsoft encoding of non breaking line in UTF-8 2019-11-26 08:57:47 +01:00
bruno DA SILVA
d367d2e864 n°524 - password policy
💚 fix CI regression (data provider no longer include classes with side effect)
2019-11-25 18:02:25 +01:00
bruno DA SILVA
9d20eba2ad 2574 - enable Password expiry
- Extensibility: The UserLocal now provide the fields needed for an extension to be able to properly handle the expiration of the password
2019-11-25 17:37:34 +01:00
bruno DA SILVA
863746852f n°524 - password policy
💚 fix CI : tests must be runned into a separate process in order to eliminate side effects of mocking the config
2019-11-25 16:53:04 +01:00
bruno DA SILVA
f416f994c9 n°2371 deprecate \MetaModel::EnumLinksClasses and \MetaModel::EnumLinkingClasses
- phpdoc: move title the to beginning of the block
2019-11-25 16:35:46 +01:00
bruno DA SILVA
70dfbbc15e n°524 - password policy
- The code now uses the standard extension method (using interfaces)
 - the metamodel can now filter on iModuleExtension in order to leverage extensions modularity (see MetaModel::EnumPlugins second param)
 - during the setup, there is no pawsord policy control
 - there is now a default policy
 - new (more precie) translation reflecting the default policy
 - fix CI?
2019-11-25 16:25:38 +01:00
Molkobain
85932eab98 N°1192 Fix crash with default parameters of the rule 2019-11-25 15:33:23 +01:00
Pierre Goiffon
0ee77d8c88 N°2163 DB*Tracked methods : modifications after review with Romain
Previous commit : 24eb82d1
Use \CMDBObject::SetTrackInfo
Move \CMDBObject::SetCurrentChange calls at the top most level of the stacks
Restore old behaviors that were removed in previous commit
2019-11-25 14:58:59 +01:00
Molkobain
2f2d9547b7 N°2603 - Portal: Fix crash when having comments in some parts of the XML 2019-11-25 12:38:34 +01:00
Molkobain
32b065708b N°1192 Add some ready-to-use navigation rules to the standard portal (and comments) 2019-11-25 09:45:31 +01:00
Molkobain
18285df154 Internal: Fix typo 2019-11-22 18:44:27 +01:00
Molkobain
b1b6c9f426 N°1192 Introduce navigation_rules in the end-users portal 2019-11-22 18:44:27 +01:00
Molkobain
ff884533f9 Internal: Add utils::ToCamelCase($sInput) function 2019-11-22 18:44:27 +01:00
Molkobain
417e80fe8d N°1192 Deprecate usage of <submit> and <cancel> tags in action rules 2019-11-22 18:44:27 +01:00
Pierre Goiffon
c203e6c7be Fix utils::StartsWith when needle bigger that value 2019-11-22 14:43:33 +01:00
bruno DA SILVA
730a0d1c98 n°524 - password policy
💚 fix CI : tests must be runned into a separate process in order to eliminate side effects of mocking the config
2019-11-22 14:38:36 +01:00
bruno DA SILVA
d9b374f723 n°524 - password policy
💚 fix CI regression (previous tests are sometimes incompatible with the default password policy)
2019-11-22 14:14:44 +01:00
bruno DA SILVA
b9cb692504 n°524 - password policy
bugfix: no rule does work properly
2019-11-22 12:31:14 +01:00
bruno DA SILVA
23fc4bb4f7 n°524 - password policy 2019-11-22 12:23:00 +01:00
Pierre Goiffon
4ae035dd51 🔧 Modify visual vertical guides setting 2019-11-22 11:42:43 +01:00
Pierre Goiffon
3a791162c5 setup module browsing perf improvement
When a module descriptor file is found, do not dig anymore in subdirectories
2nd fix, should be better than previous one (4042a12d reverted with 5ebc290b)
2019-11-21 17:44:04 +01:00
Pierre Goiffon
06791b06c4 Fix utils::EndsWith when needle bigger that value 2019-11-21 11:57:24 +01:00
Eric
5ebc290b94 Revert Setup : only scan necessary dirs for extensions 2019-11-21 11:37:22 +01:00
Pierre Goiffon
675221a15e N°2490 Setup/toolkit : no longer generates useless ALTER TABLE queries on MariaDB >= 10.2
* case insensitive SQL data type comparison
* some options have also case differences (example 'int(11) unsigned')
* DEFAULT 'NULL' added by MariaDB on all nullable fields
* default values are always surrounded with single quotes on MariaDB

This is a Combodo implementation of PR #91
2019-11-21 11:24:14 +01:00
Pierre Goiffon
2f431a0d14 🐛 Fix SynchroAttLinkSet.attribute_qualifier default value
Old typo (in a galaxy far, far away) : 1e688706
2019-11-21 10:44:54 +01:00
Molkobain
fd4e41950c N°2092 Portal: Fix missing scrollbar in tall form modals 2019-11-20 15:41:11 +01:00
Molkobain
41d5ae704a PHPDoc 2019-11-20 15:41:11 +01:00
Molkobain
83fc069bf4 Portal: Improve callback calls in CombodoPortalToolbox 2019-11-20 15:41:11 +01:00
Pierre Goiffon
32c5cd245b N°2533 Check modules manual install dir on Setup for iTop products 2019-11-20 12:04:54 +01:00
Molkobain
a259be9033 N°2311 Fix focus set to the "login" input field when pressing "tab" key 2019-11-20 12:00:52 +01:00
Molkobain
fbbdee242a Portal: Update code to use the CombodoPortalToolbox.OpenModal() helper 2019-11-20 11:38:42 +01:00
Molkobain
4a12635ea5 Portal: Introduce "CombodoPortalToolbox", helpers to ease JS manipulations especially through the iPopupMenuExtension.
- CombodoPortalToolbox.CloseAllModals() : Close all modal on the page
- CombodoPortalToolbox.OpenUrlInModal(sTargetUrl, bCloseOtherModals) : Open an URL in a modal, typically opening an object form
- CombodoPortalToolbox.OpenModal(oOptions) : Generic method to open modals in the portal with various options
2019-11-20 11:37:39 +01:00
Molkobain
db7278e9c1 🐛 Fix compiler crashing on setup due to coment in XML 2019-11-19 17:59:20 +01:00
Molkobain
10056aa4ca ✏️ Fix typos in comments 2019-11-19 17:33:07 +01:00
Pierre Goiffon
bb566df432 📝 WizardSteps documentation v2
With an ascii art schema
Thanks to http://asciiflow.com/
2019-11-19 16:31:26 +01:00
Pierre Goiffon
4042a12d39 Setup : only scan necessary dirs for extensions
We were browsing the whole hierarchy but only the root module dir was needed
This could be slow (especially when developing with a .git dir)
2019-11-19 15:27:06 +01:00
Pierre Goiffon
5ae4f9ade8 📝 WizardSteps documentation 2019-11-19 15:21:27 +01:00
Molkobain
1636f9839c Updating Symfony lib and dependencies: forgot a few files + composer.lock 2019-11-18 18:08:40 +01:00
Molkobain
c76cccd2e7 Updating Symfony lib and dependencies:
Package operations: 2 installs, 23 updates, 0 removals
  - Updating psr/log (1.1.0 => 1.1.2)
  - Updating symfony/debug (v3.4.30 => v3.4.35)
  - Updating symfony/console (v3.4.30 => v3.4.35)
  - Updating symfony/dotenv (v3.4.30 => v3.4.35)
  - Updating symfony/routing (v3.4.30 => v3.4.35)
  - Updating symfony/finder (v3.4.30 => v3.4.35)
  - Updating symfony/filesystem (v3.4.30 => v3.4.35)
  - Installing symfony/polyfill-util (v1.12.0)
  - Installing symfony/polyfill-php56 (v1.12.0)
  - Updating symfony/http-foundation (v3.4.30 => v3.4.35)
  - Updating symfony/event-dispatcher (v3.4.30 => v3.4.35)
  - Updating symfony/http-kernel (v3.4.30 => v3.4.35)
  - Updating symfony/config (v3.4.30 => v3.4.35)
  - Updating symfony/dependency-injection (v3.4.30 => v3.4.35)
  - Updating symfony/class-loader (v3.4.30 => v3.4.35)
  - Updating symfony/cache (v3.4.30 => v3.4.35)
  - Updating symfony/framework-bundle (v3.4.30 => v3.4.35)
  - Updating twig/twig (v1.42.2 => v1.42.4)
  - Updating symfony/twig-bridge (v3.4.30 => v3.4.35)
  - Updating symfony/twig-bundle (v3.4.30 => v3.4.35)
  - Updating symfony/yaml (v3.4.30 => v3.4.35)
  - Updating symfony/stopwatch (v3.4.30 => v3.4.35)
  - Updating symfony/var-dumper (v3.4.30 => v3.4.35)
  - Updating symfony/web-profiler-bundle (v3.4.30 => v3.4.35)
  - Updating symfony/css-selector (v3.4.30 => v3.4.35)
2019-11-18 18:04:32 +01:00
Eric
532eb466a1 N°1436 - Access control updated for grant_by_profile categories of classes -
Fix access to internal classes from the core engine
2019-11-18 15:36:42 +01:00
Pierre Goiffon
e8879a0455 🎨 Code formatting 2019-11-18 15:29:03 +01:00
Pierre Goiffon
ef5b4e212c N°2371 deprecate \MetaModel::EnumLinksClasses and \MetaModel::EnumLinkingClasses 2019-11-18 10:20:18 +01:00
Pierre Goiffon
24eb82d140 N°2361 Deprecate DB*Tracked methods
* update methods PHPDoc
* DBInsertTracked update callers
* DBInsertTrackedNoReload update callers
* DBUpdateTracked update callers
* DBDeleteTracked update callers
2019-11-15 17:56:04 +01:00
Pierre Goiffon
cdc8edb56b Fix backup regression "Undefined class constant 'DEFAULT_MODULE_SETTING_TIME'"
Was introduced in PR #89, woops
2019-11-15 16:44:38 +01:00
Pierre Goiffon
7235c63445 Abstract implementation for iScheduledProcess (#89)
* 📝 little PHPDoc in BackupExec
* ♻️ Create a extendable implementation of iScheduledProcess
* create AbstractWeeklyScheduledProcess
* move schedule methods to the new abstract class
* create ProcessInvalidConfigException
* in cron.php skip abstract class
2019-11-15 14:41:00 +01:00
Pierre Goiffon
fd3b33b04b N°2586 Test for iTopDesignFormat 2019-11-14 18:07:17 +01:00
Pierre Goiffon
e8815e5653 N°1283 Add migration script 2019-11-14 18:04:22 +01:00
Pierre Goiffon
551b9a3b76 \MFDocument::saveXML : $options were not passed to the callee 2019-11-14 18:04:22 +01:00
bruno DA SILVA
1c6b639992 n°1617 - iTop Fence
various improvement both in iTop and in the extension.
2019-11-14 17:02:52 +01:00
bruno DA SILVA
6fb7587d95 🔧 write the tooling methods enabling migration to/from iTop 2.7 2019-11-14 16:59:08 +01:00
Eric
3953e74cb4 🎨 refactor approot 2019-11-14 15:43:29 +01:00
Pierre Goiffon
a8f616bba7 N°2577 Setup : remove prefix for table prefix collapsible section 2019-11-14 14:46:58 +01:00
bruno DA SILVA
560eb5e071 🔧 write the tooling methods enabling migration to/from iTop 2.7 2019-11-14 12:15:30 +01:00
Eric
c665bb4761 💚 Fix unit tests 2019-11-14 09:37:39 +01:00
Denis Flaven
36584092e5 (Experimental) Export a DBSearch as an array/JSON structure. 2019-11-13 18:03:58 +01:00
Eric
30430bb7dc N°2135 - Setup callbacks for MTP 2019-11-13 17:33:56 +01:00
Eric
fdf5cff12a N°2135 - Setup callbacks for MTP 2019-11-13 15:45:33 +01:00
Eric
4816b2b0fe N°2311 - Authentication extensibility in iTop (login on specific pages and traces) 2019-11-13 15:08:34 +01:00
Eric
9c808bf2ed N°2240 - Supportability - Maintenance mode 2019-11-13 13:50:48 +01:00
Eric
7a12c2c615 N°2240 - Supportability - Maintenance mode 2019-11-13 12:00:50 +01:00
Eric
149bc9f4ef fix typo 2019-11-13 11:41:40 +01:00
Stephen Abello
b91183e9ec N°1986 Portal: Fix read-only forms from da5ccaa to work with auto-generated ones 2019-11-13 11:19:26 +01:00
Eric
b67639f9ec N°2249 - Supportability - Updater module 2019-11-13 11:08:08 +01:00
Eric
cdbcc9ce8c N°2249 - Supportability - Updater module 2019-11-13 10:42:30 +01:00
Pierre Goiffon
6c7d094921 N°2577 Setup : table prefix option is hidden also on iTop first install
Previous commit 3e785687 was hidding it only on iTop update
2019-11-12 08:54:29 +01:00
Stephen Abello
23466f6e00 📝 typo in PHPDoc 2019-11-08 17:13:15 +01:00
bruno DA SILVA
08c1f4f072 autoload rework
- bootstrap.inc.php is now included by approot.inc.php
 - remove all unescessaries includes of bootstrap.inc.php
 - in bootstrap.inc.php autoload can be bypassed using a feature flag because "why not"
2019-11-08 16:51:57 +01:00
Pierre Goiffon
1194c5c7fe 🎨 sla-computation : fix PHPDoc + format code 2019-11-08 15:30:47 +01:00
Eric
f63fb16233 N°2135 - Setup callbacks for MTP 2019-11-08 14:05:24 +01:00
Eric
a4143df36a N°2240 - Supportability - Maintenance mode 2019-11-07 16:45:52 +01:00
Pierre Goiffon
3e78568755 N°2577 Setup : table prefix option is now hidden by default
Should be used only by a few portion of users
2019-11-07 16:34:25 +01:00
Eric
9b14cd7633 N°2240 - Supportability - Maintenance mode 2019-11-07 15:23:42 +01:00
Eric
2b2488f376 N°2240 - Supportability - Maintenance mode 2019-11-07 15:21:48 +01:00
Pierre Goiffon
fd77554cb7 N°2555 action_rule : fix add_to_list regression
Loop was removed in 5c483efd but we need it !
Sample rule that needs such a loop :
				<action_rule id="N2555_multiple_results_addtolist" _delta="define">
					<source_oql>SELECT Person WHERE id > 0</source_oql>
					<presets>
						<preset id="1">add_to_list(id, contacts_list)</preset>
					</presets>
				</action_rule>
2019-11-07 14:56:39 +01:00
Eric
2ee3d27ba8 N°2240 - Supportability - Maintenance mode (revert) 2019-11-07 14:15:34 +01:00
Eric
af24f46803 N°2249 - Supportability - Updater module 2019-11-07 13:24:48 +01:00
Eric
da1684a8b9 N°2249 - Supportability - Updater module 2019-11-07 13:14:20 +01:00
Eric
5e7ae930c5 N°2240 - Supportability - Maintenance mode 2019-11-07 12:36:35 +01:00
Stephen Abello
541226356c 📝 typo in PHPDoc 2019-11-07 11:38:08 +01:00
Eric
a30345c96c N°2249 - Supportability - Updater module 2019-11-07 10:00:05 +01:00
Eric
d035130d00 N°2240 - Supportability - Maintenance mode 2019-11-07 09:16:06 +01:00
Pierre Goiffon
7d8181c44f N°2576 Fix "invalid numeric value" when inserting/updating AttributeDecimal
Was happening on certain configurations with a non EN-US user locale
2019-11-06 18:15:40 +01:00
bruno DA SILVA
45536cf957 🐛 fix regression introduced by the PR n°98
the realpath removed the trailing / wich is required
2019-11-06 10:00:55 +01:00
Stephen Abello
da5ccaaa85 N°1986 Portal: Forms with only transition buttons are now read-only 2019-11-05 12:17:54 +01:00
jbostoen
7c773991e7 Version 20191104-1550
* fix setup crashing due to realpath() returning lowercase path on Windows
2019-11-05 08:55:41 +01:00
bruno DA SILVA
bf976e5b8f typo (bis)
ahhh!
2019-11-04 15:07:47 +01:00
bruno DA SILVA
d9ad3f5e98 typo & unused variable removal
thank you @Hipska !
2019-11-04 15:02:19 +01:00
Eric
9054dcb9ff N°2517 - Supportability : system report (user name) 2019-11-04 12:06:49 +01:00
bruno DA SILVA
57116ef054 1627 - Ticket ref sometimes duplicate
This code is a drop in replacement based on  an abstract counter based on an abstract keyValue store.
The counter can be
 - class based (in this case the counter is initialized on max(id) + 1
 - key based (in this case the counter starts at 0)

Important: on both cases the counter is no more kept aligned with the primary key.

This lead to a MySQL8 compatible implementation.
2019-11-04 11:50:36 +01:00
bruno DA SILVA
c6f5b8b1f9 1627 - Ticket ref sometimes duplicate
This code is a drop in replacement based on  an abstract counter based on an abstract keyValue store.
The counter can be
 - class based (in this case the counter is initialized on max(id) + 1
 - key based (in this case the counter starts at 0)

Important: on both cases the counter is no more kept aligned with the primary key.

This lead to a MySQL8 compatible implementation.
2019-11-04 11:50:36 +01:00
Pierre Goiffon
9e015ba59a 📝 UI better log on catch 2019-11-04 09:22:43 +01:00
Pierre Goiffon
6144cfad3d 📝 Link between Dict::Format and TemplateString 2019-10-31 10:35:48 +01:00
Stephen Abello
3844a18b86 N°971 Portal: Allow n:n links for Browse Brick's levels 2019-10-30 16:41:58 +01:00
Stephen Abello
e88c6e9583 📝 typo in PHPDoc 2019-10-30 16:41:58 +01:00
Pierre Goiffon
6793fb2a35 N°2293 update PHPDoc on callbacks 2019-10-30 16:26:30 +01:00
bruno DA SILVA
4834c326aa 2498 - restrict access to assets into env-*
- allow static html into extensions/ and datamodels/
 - allow direct access to php into env-* for legacy code taht do not use exec.php
2019-10-30 15:46:33 +01:00
Pierre Goiffon
e6167adefd N°2518 ACE Editor : SQL mode 2019-10-30 10:11:33 +01:00
Pierre Goiffon
a8c3756ba7 Rename licenses file
Fix typo in file name
2019-10-30 10:11:33 +01:00
Pierre Goiffon
34b47f0239 N°2518 update licenses to reflect ACE editor moved from itop-config to iTop core 2019-10-30 10:11:33 +01:00
bruno DA SILVA
0d13d9eabe 2498 - restrict access to assets into env-*
- allow static content within datamodels/ and extensions/ (reason: the setup make use of images within this directory)
 - simplified .htaccess and web.content generation during the compilation
 - it now also allow fonts
2019-10-30 09:44:28 +01:00
Pierre Goiffon
358adb2109 📝 Some PHPDoc complement 2019-10-28 16:19:30 +01:00
odain
92285b55b6 add few tests on OQL group by 2019-10-28 15:50:53 +01:00
Pierre Goiffon
dda91be6d1 N°2555 action_rules : an invalid rule won't prevent anymore others to execute
This blocking scenario was added in 5c483efd
2019-10-25 15:56:26 +02:00
Eric
bec4dbafd2 N°2315 - Login screen extensibility API refactor 2019-10-25 14:35:24 +02:00
Eric
dcb46990c2 N°1436 - Search retrieve users belonging to not allowed Org 2019-10-25 10:31:47 +02:00
Pierre Goiffon
fe03342b6f 🌐 Fix typo
SF#1812, many thanks Lars Scheibling !
2019-10-25 09:37:22 +02:00
Eric
ee037acd34 N°2315 - Login screen extensibility API refactor 2019-10-25 08:32:29 +02:00
Pierre Goiffon
d4a696cb6b N°2555 Specific exception for invalid action_rules 2019-10-24 18:05:58 +02:00
Pierre Goiffon
5c483efd15 N°2555 action_rule performance
* do not execute scope_oql that have no conditions
* pick the first result only
2019-10-24 17:24:09 +02:00
Pierre Goiffon
9aeba1df9b N°2555 New \DBSearch::GetFirstResult method 2019-10-24 17:24:09 +02:00
bruno DA SILVA
e98683ae1c fix regression introduced in the carbon branch 2019-10-24 16:19:07 +02:00
Eric
c552e73d20 N°2315 - Login screen extensibility API refactor 2019-10-24 14:56:01 +02:00
Stephen Abello
1851163cee N°1881 Portal: Show confirmation dialog when closing forms with unsaved data 2019-10-24 10:51:49 +02:00
Pierre Goiffon
3667f95b7c N°2558 center is back in sanitizer white list
Reverts 4450d6af (2.5.0)
Was causing troubles when integrating emails
2019-10-24 10:20:47 +02:00
Eric
b6796d2742 N°679 - Database inconsistency during INSERT
N°2499
2019-10-24 09:00:56 +02:00
Eric
047983cb91 🎨 Login screen extensibility 2019-10-23 17:27:04 +02:00
Eric
8455abdfe9 N°2315 - Markup extensibility: interface refactor 2019-10-23 16:49:00 +02:00
Eric
b7c3fbb176 💚 Refactor unit tests 2019-10-23 16:41:28 +02:00
Eric
5642552f9a N°1888 - Filter search with another search 2019-10-23 12:56:02 +02:00
Eric
6f6b654dba N°1113 - Global search only searchable attributes 2019-10-23 10:24:37 +02:00
Pierre Goiffon
c8b791efd3 Merge remote-tracking branch 'origin/itop-carbon' into develop
# Conflicts:
#	application/utils.inc.php
#	setup/ajax.dataloader.php
2019-10-22 18:27:13 +02:00
Pierre Goiffon
52fd58cd93 Remove "s" JQueryUI resizable handle override
Better to override locally O:)
2019-10-22 16:45:25 +02:00
Pierre Goiffon
66d638e224 💄 N°2518 New JQueryUI "s" handler style 2019-10-22 16:06:32 +02:00
Pierre Goiffon
16c4f18a81 N°2518 fix log call is crashing when invalid log_filename_builder_impl value set in config 2019-10-22 15:15:40 +02:00
Pierre Goiffon
cd6104ddb3 N°2518 If switching to log file rotation, rename setup/error legacy files 2019-10-22 15:08:48 +02:00
Pierre Goiffon
4fe7cd5adc 🎨 Config file code formating 2019-10-22 15:08:48 +02:00
Eric
3f59141407 N°1114 - config-itop.php access rights enforcement 2019-10-22 15:02:51 +02:00
Pierre Goiffon
03b8ed5ce4 Merge branch 'support/2.6.2' 2019-10-07 09:41:08 +02:00
Pierre Goiffon
a625733885 👷 Jenkins : fix jenkinsfile filename case 2019-09-10 14:23:36 +02:00
Eric
b0414748cb Typo 2019-06-13 11:55:10 +02:00
Molkobain
b6418d95e7 Add PHPDoc for type hinting as iTop replaces \DOMDocument with \MFDocument 2019-05-28 10:36:18 +02:00
Molkobain
2d6251e5df 💄 Add warning message CSS class (like error message) 2019-05-15 17:53:48 +02:00
Molkobain
91f410a85c 💄 Fix images being too large in icon selector (dashboards and Designer)
Note: The widget still needs a more aggressive refactoring to render nicely...
2019-05-02 16:42:21 +02:00
Eric
0a48696cd8 Carbon: N°1855 - Fix: Cannot remove last 'dependencies' with UI
Note that multi-select value when no entry is selected is "" and not []
in order to be posted, so multi-select values are not always arrays.
2019-04-30 15:46:44 +02:00
Eric
2f71570390 Carbon: N°1855 - fix "depends on" displaying fields from children 2019-04-30 12:02:13 +02:00
Molkobain
78b6c03af7 💡 Add some PHPDoc 2019-04-25 12:46:57 +02:00
Molkobain
22342cdc05 🐛 N°2184 Fix validation issue when several label fiels in a dashlet/designer form 2019-04-25 12:42:16 +02:00
Molkobain
dcf4963e0c N°2152 Fix bad XML generation when adding a dashboard attribute on a new class 2019-04-16 17:15:15 +02:00
Eric
9ec36a76f6 Carbon: N°1589 - Check migration 2019-04-10 12:12:31 +02:00
Eric
09b470e6c7 Better output 2019-04-09 10:43:39 +02:00
Molkobain
40151c7a43 N°2147 Fix non working impact relation when based on default value 2019-04-05 16:25:26 +02:00
Molkobain
34c030b501 N°2070 Extend ModelFactory implementations to optionally check meta classes (PHP) along with regular XML classes 2019-04-05 15:48:29 +02:00
Molkobain
c59d3cc624 Fix unreachable log message on exception 2019-04-01 16:52:22 +02:00
3226 changed files with 71945 additions and 154050 deletions

View File

@@ -23,7 +23,7 @@ Some iTop specific tags were added :
### known limitations:
#### `@see` tags must be very specific:
* always prefix class members with `ClassName::`
* always prefix class members (attributes or methods) with `ClassName::` (do not use self)
* for methods always suffix them with `()`,
* do not reference variables since they are not documented. If you have to, always prefix them with `$`

3015
.doc/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = 140
ij_visual_guides = 80, 120, 140
ij_wrap_on_typing = true
[*.css]

4
.gitignore vendored
View File

@@ -41,6 +41,10 @@ test/vendor/*
!/.idea/inspectionProfiles
!/.idea/inspectionProfiles/*
# doc. generation
/.doc/vendor
#phpdocumentor temp file
ast.dump

View File

@@ -67,11 +67,6 @@
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SCSS">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="WRAP_ON_TYPING" value="1" />
</codeStyleSettings>

View File

@@ -1,9 +1,77 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Combodo" />
<inspection_tool class="CascadeStringReplacementInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="ERROR" enabled_by_default="true">
<option name="configuration">
<list>
<option value="\Codeception\Util\Debug::debug" />
<option value="\Codeception\Util\Debug::pause" />
<option value="\Doctrine\Common\Util\Debug::dump" />
<option value="\Doctrine\Common\Util\Debug::export" />
<option value="\Illuminate\Support\Debug\Dumper::dump" />
<option value="\Symfony\Component\Debug\Debug::enable" />
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
<option value="\Zend\Debug\Debug::dump" />
<option value="\Zend\Di\Display\Console::export" />
<option value="dd" />
<option value="debug_print_backtrace" />
<option value="debug_zval_dump" />
<option value="dpm" />
<option value="dpq" />
<option value="dsm" />
<option value="dump" />
<option value="dvm" />
<option value="error_log" />
<option value="kpr" />
<option value="phpinfo" />
<option value="print_r" />
<option value="var_dump" />
<option value="var_export" />
<option value="xdebug_break" />
<option value="xdebug_call_class" />
<option value="xdebug_call_file" />
<option value="xdebug_call_function" />
<option value="xdebug_call_line" />
<option value="xdebug_code_coverage_started" />
<option value="xdebug_debug_zval" />
<option value="xdebug_debug_zval_stdout" />
<option value="xdebug_dump_superglobals" />
<option value="xdebug_enable" />
<option value="xdebug_get_code_coverage" />
<option value="xdebug_get_collected_errors" />
<option value="xdebug_get_declared_vars" />
<option value="xdebug_get_function_stack" />
<option value="xdebug_get_headers" />
<option value="xdebug_get_monitored_functions" />
<option value="xdebug_get_profiler_filename" />
<option value="xdebug_get_stack_depth" />
<option value="xdebug_get_tracefile_name" />
<option value="xdebug_is_enabled" />
<option value="xdebug_memory_usage" />
<option value="xdebug_peak_memory_usage" />
<option value="xdebug_print_function_stack" />
<option value="xdebug_start_code_coverage" />
<option value="xdebug_start_error_collection" />
<option value="xdebug_start_function_monitor" />
<option value="xdebug_start_trace" />
<option value="xdebug_stop_code_coverage" />
<option value="xdebug_stop_error_collection" />
<option value="xdebug_stop_function_monitor" />
<option value="xdebug_stop_trace" />
<option value="xdebug_time_index" />
<option value="xdebug_var_dump" />
</list>
</option>
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="HtmlRequiredAltAttribute" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlRequiredLangAttribute" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IsNullFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpComposerExtensionStubsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpIncludeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
@@ -23,6 +91,61 @@
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="DONT_REPORT_ABSTRACT_CLASS" value="true" />
</inspection_tool>
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration">
<list>
<option value="barryvdh/laravel-debugbar" />
<option value="behat/behat" />
<option value="brianium/paratest" />
<option value="codeception/codeception" />
<option value="codedungeon/phpunit-result-printer" />
<option value="composer/composer" />
<option value="doctrine/coding-standard" />
<option value="filp/whoops" />
<option value="friendsofphp/php-cs-fixer" />
<option value="humbug/humbug" />
<option value="infection/infection" />
<option value="jakub-onderka/php-parallel-lint" />
<option value="johnkary/phpunit-speedtrap" />
<option value="kalessil/production-dependencies-guard" />
<option value="mikey179/vfsStream" />
<option value="mockery/mockery" />
<option value="mybuilder/phpunit-accelerator" />
<option value="orchestra/testbench" />
<option value="pdepend/pdepend" />
<option value="phan/phan" />
<option value="phing/phing" />
<option value="phpcompatibility/php-compatibility" />
<option value="phpmd/phpmd" />
<option value="phpro/grumphp" />
<option value="phpspec/phpspec" />
<option value="phpspec/prophecy" />
<option value="phpstan/phpstan" />
<option value="phpunit/phpunit" />
<option value="povils/phpmnd" />
<option value="roave/security-advisories" />
<option value="satooshi/php-coveralls" />
<option value="sebastian/phpcpd" />
<option value="slevomat/coding-standard" />
<option value="spatie/phpunit-watcher" />
<option value="squizlabs/php_codesniffer" />
<option value="sstalle/php7cc" />
<option value="symfony/debug" />
<option value="symfony/maker-bundle" />
<option value="symfony/phpunit-bridge" />
<option value="symfony/var-dumper" />
<option value="vimeo/psalm" />
<option value="wimg/php-compatibility" />
<option value="wp-coding-standards/wpcs" />
<option value="yiisoft/yii2-coding-standards" />
<option value="yiisoft/yii2-debug" />
<option value="yiisoft/yii2-gii" />
<option value="zendframework/zend-coding-standard" />
<option value="zendframework/zend-debug" />
<option value="zendframework/zend-test" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />

View File

@@ -2,6 +2,8 @@
set -x
whoami
pwd
ls

View File

@@ -3,6 +3,20 @@ set -x
cd test
export DEBUG_UNIT_TEST="0"
export DEBUG_UNIT_TEST=0
RUN_NONREG_TESTS=0
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
if [ $# -ge 1 -a "x$1" == "xtrue" ]
then
export DEBUG_UNIT_TEST=1
else
export DEBUG_UNIT_TEST=0
fi
if [ $# -ge 2 -a "x$2" == "xtrue" ]
then
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
else
#echo php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --exclude-group OQL --teamcity
fi

View File

@@ -2,5 +2,7 @@
set -x
chmod 666 conf/production/config-itop.php
cd toolkit
php unattended_install.php default-params.xml
php unattended_install.php --response_file=default-params.xml --clean=true

View File

@@ -80,7 +80,7 @@ $MySettings = array(
'db_host' => '',
'db_name' => 'itop_ci_main',
'db_name' => 'itop_ci',
'db_pwd' => 'IKnowYouSeeMeInJenkinsConf',
@@ -247,6 +247,9 @@ $MySettings = array(
*
*/
$MyModuleSettings = array(
'authent-local' => array (
'password_validation.pattern' => '',
),
'itop-attachments' => array (
'allowed_classes' => array (
0 => 'Ticket',

View File

@@ -7,7 +7,7 @@
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.5.0</datamodel_version>
<previous_configuration_file>/var/lib/jenkins/workspace/iTop-CI/unattended_install/default-config-itop.php</previous_configuration_file>
<previous_configuration_file>default-config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>

View File

@@ -20,7 +20,6 @@
//this scrit will be run under the ./toolkit directory, relatively to the document root
require_once('../approot.inc.php');
require_once(APPROOT.'/bootstrap.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/clipage.class.inc.php');
require_once(APPROOT.'/core/config.class.inc.php');

6
.make/README.md Normal file
View File

@@ -0,0 +1,6 @@
= Make Doc =
.make folder is meant to gather tools for releasing process. Maybe other new purposes will come as well....
== license ==
- updateLicenses.php: used to update community-licenses.xml easily based on composer.json files
- sortLicenceXml.php: used to sort licenses based on scope + product name

View File

@@ -0,0 +1,98 @@
<?php
/**
* Copyright (C) 2010-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
* along with iTop. If not, see <http: *www.gnu.org/licenses/>
*
*/
$iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php");
$sApproot = APPROOT;
$aTrace = array();
$aParamsConfig = array(
'composer-path' => array(
'default' => 'composer.phar',
)
);
$aParamsConfigNotFound = array_flip(array_keys($aParamsConfig));
$aGivenArgs = $argv;
unset($aGivenArgs[0]);
$aParams = array();
foreach ($aParamsConfig as $sParam => $aConfig)
{
$bParamsFound = false;
foreach ($aGivenArgs as $sGivenArg)
{
if (preg_match("/--$sParam(?:=(?<value>.*))?$/", $sGivenArg, $aMatches))
{
$aParams[$sParam] =
isset($aMatches['value'])
? $aMatches['value']
: true
;
$bParamsFound = true;
unset($aGivenArgs[$sGivenArg]);
}
}
if ($bParamsFound)
{
unset($aParamsConfigNotFound[$sParam]);
}
}
foreach ($aParamsConfigNotFound as $sParamsConfigNotFound => $void)
{
if (isset($aParamsConfig[$sParamsConfigNotFound]['default']))
{
$aParams[$sParamsConfigNotFound] = $aParamsConfig[$sParamsConfigNotFound]['default'];
$aTrace[] = "\e[1;30mUsing default value '{$aParams[$sParamsConfigNotFound]}' for '$sParamsConfigNotFound'\e[0m\n";
continue;
}
die("Missing '$sParamsConfigNotFound'");
}
echo "This command aims at helping you find upgradable dependencies\n";
echo "\e[0;33mBeware of the version colored in orange, they probably introduce BC breaks!\e[0m\n";
$sCommand = "{$aParams['composer-path']} show -loD --working-dir=$sApproot --ansi";
$execCode = exec($sCommand, $output);
$sOutput = implode("\n", $output)."\n";
if (!$execCode)
{
echo "\e[41mFailed to execute '$sCommand'\e[0m\n";
echo "Trace: \n".implode("\n", $aTrace);
}
else
{
$iCountDepdendenciesFound = count($output);
$iCountBc = substr_count($sOutput, '[33m');
echo sprintf("Found \033[44m%d\033[0m upgradable dependencies, including \e[41m%s BC break\e[0m 😱 :\n\n", $iCountDepdendenciesFound, $iCountBc);
}
echo $sOutput;

View File

@@ -0,0 +1,57 @@
<?php
/**
* Copyright (C) 2010-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
* along with iTop. If not, see <http: *www.gnu.org/licenses/>
*
*/
use Combodo\iTop\Composer\iTopComposer;
$iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php");
require_once (APPROOT."/setup/setuputils.class.inc.php");
if (php_sapi_name() !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
clearstatcache();
$oiTopComposer = new iTopComposer();
$aDeniedButStillPresent = $oiTopComposer->ListDeniedButStillPresent();
foreach ($aDeniedButStillPresent as $sDir)
{
if (! preg_match('#[tT]ests?/?$#', $sDir))
{
echo "\nfound INVALID denied test dir: '$sDir'\n";
throw new \Exception("$sDir must end with /Test/ or /test/");
}
try
{
SetupUtils::rrmdir($sDir);
echo "Remove denied test dir: '$sDir'\n";
}
catch (\Exception $e)
{
echo "\nFAILED to remove denied test dir: '$sDir'\n";
}
}

View File

@@ -0,0 +1,90 @@
#/bin/bash
#git diff --name-status 2.6.2..HEAD js |grep 'A\sjs/' |awk -F/ '{printf("lib/%s/%s\n",$2,$3)}'|sort |uniq >/tmp/toto
#git diff --name-status 2.6.2..HEAD lib |grep 'A\slib/' |awk -F/ '{printf("lib/%s/%s\n",$2,$3)}'|sort |uniq >/tmp/toto
function HELP(){
echo " Syntax: bash $0 /var/www/html/iTop"
}
if [ $# -eq 0 ]
then
echo "no iTop path provided"
HELP
exit 1
fi
iTopPath=$1
if [ ! -d $iTopPath ]
then
echo "$iTopPath is not an iTop path."
HELP
exit 1
fi
echo "<?xml version=\"1.0\"?>
<licenses>"
for subfolder in lib datamodels
do
for l in $(find $iTopPath/$subfolder/ -name composer.json|sed 's|/composer.json||')
do
if [ ! -d $l ]
then
continue
fi
if [ "$subfolder" == "datamodels" ]
then
if [ $(find $l -name module*.php|wc -l) -ne 0 -o $(echo "$l"|grep -c "itop-portal-base") -ne 0 ]
then
continue
fi
fi
dir=$(dirname $(dirname $l))
prod=$(echo $l| sed "s|$dir/||1")
echo $l $subfolder
lictype=$(cd $l && composer licenses --format json |jq .license[] |sed 's|\"||g')
authors=""
if [ -f $l/composer.json ]
then
author_nb=$(grep -c authors $l/composer.json|sed 's| ||g')
if [ "x$author_nb" != "x0" ]
then
OLDIFS=$IFS
IFS=$'\n'
for a in $(cat $l/composer.json |jq .authors[].name|sed 's|\"||g')
do
authors="$authors$a - "
done
authors="$authors#"
authors=$(echo $authors |sed 's| - #||')
IFS=$OLDIFS
fi
fi
lic=""
for licf in $(find $l -name LICEN*)
do
lic=$(cat $licf)
break
done
#if [ "x$lic" == "x" ]
#then
# echo "============== no license found $l"
#fi
echo " <license>
<product scope=\"$subfolder\">$prod</product>
<author>$authors</author>
<license_type>$lictype</license_type>
<text><![CDATA[
$lic
]]></text>
</license>"
done
done
echo "</licenses>"

View File

@@ -0,0 +1,64 @@
<?php
/**
* script used to sort license file (usefull for autogeneration)
* Example:
*/
$iTopFolder = __DIR__ . "/../../" ;
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
$dom = new DOMDocument();
$dom->load($xmlFilePath);
$xp = new DOMXPath($dom);
$licenseList = $xp->query('/licenses/license');
$licenses = iterator_to_array($licenseList);
function get_scope($product_node)
{
$scope = $product_node->getAttribute("scope");
if ($scope === "")
{ //put iTop first
return "aaaaaaaaa";
}
return $scope;
}
function get_product_node($license_node)
{
foreach ($license_node->childNodes as $child)
{
if (is_a($child, 'DomElement') && $child->tagName === "product")
{
return $child;
}
}
return null;
}
function sort_by_product($a, $b)
{
$aProductNode = get_product_node($a);
$bProductNode = get_product_node($b);
$res = strcmp(get_scope($aProductNode), get_scope($bProductNode));
if ($res !== 0)
{
return $res;
}
//sort on node product name
return strcmp($aProductNode->nodeValue, $bProductNode->nodeValue);
}
usort($licenses, 'sort_by_product');
$newdom = new DOMDocument("1.0");
$newdom->formatOutput = true;
$root = $newdom->createElement("licenses");
$newdom->appendChild($root);
foreach ($licenses as $b) {
$node = $newdom->importNode($b,true);
$root->appendChild($newdom->importNode($b,true));
}
$newdom->save($xmlFilePath);

View File

@@ -0,0 +1,89 @@
<?php
/**
* script used to sort license file (usefull for autogeneration)
* Example: php
*/
$iTopFolder = __DIR__ . "/../../" ;
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
function get_scope($product_node)
{
$scope = $product_node->getAttribute("scope");
if ($scope === "")
{ //put iTop first
return "aaaaaaaaa";
}
return $scope;
}
function get_product_node($license_node)
{
foreach ($license_node->childNodes as $child)
{
if (is_a($child, 'DomElement') && $child->tagName === "product")
{
return $child;
}
}
return null;
}
function sort_by_product($a, $b)
{
$aProductNode = get_product_node($a);
$bProductNode = get_product_node($b);
$res = strcmp(get_scope($aProductNode), get_scope($bProductNode));
if ($res !== 0)
{
return $res;
}
//sort on node product name
return strcmp($aProductNode->nodeValue, $bProductNode->nodeValue);
}
function get_license_nodes($file_path)
{
$dom = new DOMDocument();
$dom->load($file_path);
$xp = new DOMXPath($dom);
$licenseList = $xp->query('/licenses/license');
$licenses = iterator_to_array($licenseList);
usort($licenses, 'sort_by_product');
return $licenses;
}
$old_licenses = get_license_nodes($xmlFilePath);
//generate file with updated licenses
$generated_license_file_path = __DIR__."/provfile.xml";
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
$new_licenses = get_license_nodes($generated_license_file_path);
exec("rm -f ". $generated_license_file_path);
foreach ($old_licenses as $b) {
$aProductNode = get_product_node($b);
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
{
$new_licenses[] = $b;
}
}
usort($new_licenses, 'sort_by_product');
$new_dom = new DOMDocument("1.0");
$new_dom->formatOutput = true;
$root = $new_dom->createElement("licenses");
$new_dom->appendChild($root);
foreach ($new_licenses as $b) {
$node = $new_dom->importNode($b,true);
$root->appendChild($new_dom->importNode($b,true));
}
$new_dom->save($xmlFilePath);

View File

@@ -0,0 +1,47 @@
<?php
/*******************************************************************************
* Tool to automate version update before release
*
* Will update version in the following files :
*
* * datamodels/2.x/.../module.*.php
* * datamodels/2.x/version.xml
* * css/css-variables.scss $version
*
* Usage :
* `php .make\release\update-versions.php "2.7.0-rc"`
*
* @since 2.7.0
******************************************************************************/
require_once (__DIR__.'/../../approot.inc.php');
require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
/** @var \FileVersionUpdater[] $aFilesUpdaters */
$aFilesUpdaters = array(
new iTopVersionFileUpdater(),
new CssVariablesFileUpdater(),
new DatamodelsModulesFiles(),
);
if (count($argv) === 1)
{
echo '/!\ You must pass the new version as parameter';
exit(1);
}
$sVersionLabel = $argv[1];
if (empty($sVersionLabel))
{
echo 'Version passed as parameter is empty !';
exit(2);
}
foreach ($aFilesUpdaters as $oFileVersionUpdater)
{
$oFileVersionUpdater->UpdateAllFiles($sVersionLabel);
}

View File

@@ -0,0 +1,36 @@
<?php
/*******************************************************************************
* Tool to automate datamodel version update in XML
*
* Will update version in the following files :
*
* datamodels/2.x/.../datamodel.*.xml
*
* Usage :
* `php .make\release\update-xml.php "1.7"`
*
* @since 2.7.0
******************************************************************************/
require_once (__DIR__.'/../../approot.inc.php');
require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
if (count($argv) === 1)
{
echo '/!\ You must pass the new version as parameter';
exit(1);
}
$sVersionLabel = $argv[1];
if (empty($sVersionLabel))
{
echo 'Version passed as parameter is empty !';
exit(2);
}
$oFileVersionUpdater = new DatamodelsXmlFiles();
$oFileVersionUpdater->UpdateAllFiles($sVersionLabel);

View File

@@ -0,0 +1,169 @@
<?php
/*******************************************************************************
* Classes for updater tools
*
* @see update-versions.php
* @see update-xml.php
******************************************************************************/
require_once (__DIR__.'/../../approot.inc.php');
abstract class FileVersionUpdater
{
/**
* @return string[] full path of files to modify
*/
abstract public function GetFiles();
/**
* Warnign : will consume lots of memory on larger files !
*
* @param string $sVersionLabel
* @param string $sFileContent
* @param string $sFileFullPath
*
* @return string file content with replaced values
*/
abstract public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath);
public function UpdateAllFiles($sVersionLabel)
{
$aFilesToUpdate = $this->GetFiles();
$sFileUpdaterName = get_class($this);
echo "# Updater : $sFileUpdaterName\n";
foreach ($aFilesToUpdate as $sFileToUpdateFullPath)
{
try
{
$sCurrentFileContent = file_get_contents($sFileToUpdateFullPath);
$sNewFileContent = $this->UpdateFileContent($sVersionLabel, $sCurrentFileContent, $sFileToUpdateFullPath);
file_put_contents($sFileToUpdateFullPath, $sNewFileContent);
echo " - $sFileToUpdateFullPath : OK !\n";
}
catch (Exception $e)
{
echo " - $sFileToUpdateFullPath : Error :(\n";
}
}
}
}
abstract class AbstractSingleFileVersionUpdater extends FileVersionUpdater
{
private $sFileToUpdate;
public function __construct($sFileToUpdate)
{
$this->sFileToUpdate = $sFileToUpdate;
}
public function GetFiles()
{
return array(APPROOT.$this->sFileToUpdate);
}
}
class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
{
public function __construct()
{
parent::__construct('datamodels/2.x/version.xml');
}
/**
* @inheritDoc
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
return preg_replace(
'/(<version>)[^<]*(<\/version>)/',
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}
class CssVariablesFileUpdater extends AbstractSingleFileVersionUpdater
{
public function __construct()
{
parent::__construct('css/css-variables.scss');
}
/**
* @inheritDoc
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
return preg_replace(
'/(\$version: "v)[^"]*(";)/',
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}
abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater
{
protected $sGlobPattern;
public function __construct($sGlobPattern)
{
$this->sGlobPattern = $sGlobPattern;
}
public function GetFiles()
{
return glob($this->sGlobPattern);
}
}
class DatamodelsModulesFiles extends AbstractGlobFileVersionUpdater
{
public function __construct()
{
parent::__construct(APPROOT.'datamodels/2.x/*/module.*.php');
}
/**
* @inheritDoc
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
$sModulePath = realpath($sFileFullPath);
$sModuleFileName = basename($sModulePath, 1);
$sModuleName = preg_replace('/[^.]+\.([^.]+)\.php/', '$1', $sModuleFileName);
return preg_replace(
"/('$sModuleName\/)[^']+(')/",
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}
class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
{
public function __construct()
{
parent::__construct(APPROOT.'datamodels/2.x/*/datamodel.*.xml');
}
/**
* @inheritDoc
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
return preg_replace(
'/(<itop_design .* version=")[^"]+(">)/',
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}

View File

@@ -10,8 +10,8 @@ Here are some guidelines that will help us integrate your work!
### Subjects
You are welcome to create pull requests on any of those subjects:
* 🐛 `:bug:` bug fix
* 🌐 `:globe_with_meridians:` translation / i18n / l10n
* 🐛 bug fix
* 🌐 translation / i18n / l10n
If you want to implement a **new feature**, please [create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) for review.
If you ever want to begin implementation, do so in a fork, and add a link to the corresponding commits in the ticket.
@@ -27,7 +27,7 @@ If you have an idea you're sure would benefit to all of iTop users, you may
[create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) to submit it, but be warned that there are lots of good
reasons to refuse such changes.
### License
### 📄 License
iTop is distributed under the AGPL-3.0 license (see the [license.txt] file),
your code must comply with this license.
@@ -37,7 +37,7 @@ If you want to use another license, you may [create an extension][wiki new ext].
[wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension
## Branch model
## 🔀 Branch model
TL;DR:
> **create a fork from iTop main repository,
@@ -80,7 +80,7 @@ That may be different if you want to fix a bug, please use develop anyway and as
## Coding
### PHP styleguide
### 🎨 PHP styleguide
Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards).
@@ -88,7 +88,7 @@ Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acust
A [dedicated page](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Atranslation) is available in the official wiki.
### Tests
### Tests
Please create tests that covers as much as possible the code you're submitting.
@@ -117,7 +117,7 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
* 💄 `:lipstick:` Updating the UI and style files.
## Pull request
## 👥 Pull request
When your code is working, please:

6
Jenkinsfile vendored
View File

@@ -1,5 +1,9 @@
pipeline {
agent any
parameters {
booleanParam(name: 'debugMode', defaultValue: 'false', description: 'Debug mode?')
booleanParam(name: 'runNonRegOQLTests', defaultValue: 'false', description: 'Do You want to run legacy OQL regression tests?')
}
stages {
stage('init') {
@@ -36,7 +40,7 @@ pipeline {
parallel {
stage('phpunit') {
steps {
sh './.jenkins/bin/tests/phpunit.sh'
sh './.jenkins/bin/tests/phpunit.sh ${debugMode} ${runNonRegOQLTests}'
}
}
}

View File

@@ -51,17 +51,30 @@ iTop also offers mass import tools and web services to integrate with your IT
## Last releases
### Versions 2.7.*
- 2.7.0-beta2 published on January 29, 2020
- [Changes since the previous version][62]
- [New features][63]
- [Migration notes][64]
- [Download iTop 2.7.0-beta2][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-beta2
### 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.1][61]
- [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.1
[61]: https://sourceforge.net/projects/itop/files/itop/2.6.3
### Versions 2.5.*
@@ -75,20 +88,6 @@ iTop also offers mass import tools and web services to integrate with your IT
[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
### Versions 2.4.*
- 2.4.0 published on November 16, 2017
- [Changes since the previous version][50]
- [New features][51]
- [Migration notes][52]
- [Download iTop 2.4.1][53]
[50]: https://www.itophub.io/wiki/page?id=2_4_0:release:change_log
[51]: https://www.itophub.io/wiki/page?id=2_4_0:release:2_4_whats_new
[52]: https://www.itophub.io/wiki/page?id=2_4_0:install:230_to_240_migration_notes
[53]: https://sourceforge.net/projects/itop/files/itop/2.4.1
## About Us
@@ -112,6 +111,7 @@ We would like to give a special thank you to the people from the community who c
- Casteleyn, Thomas
- Castro, Randall Badilla
- Colantoni, Maria Laura
- Couronné, Guy
- Dvořák, Lukáš
- Goethals, Stefan
- Gumble, David
@@ -121,6 +121,7 @@ We would like to give a special thank you to the people from the community who c
- Konečný, Kamil
- Kunin, Vladimir
- Lassiter, Dennis
- Lazcano, Federico
- Lucas, Jonathan
- Malik, Remie
- Rosenke, Stephan
@@ -143,4 +144,5 @@ We would like to give a special thank you to the people from the community who c
### Companies
- Hardis
- ITOMIG
- Pimkie

View File

@@ -121,20 +121,15 @@ class UserRightsMatrix extends UserRightsAddOnAPI
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
// Maybe we should check that no other user with userid == 0 exists
CMDBObject::SetTrackInfo('Initialization');
$oUser = new UserLocal();
$oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd);
$oUser->Set('contactid', 1); // one is for root !
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
// Create a change to record the history of the User object
$oChange = MetaModel::NewObject("CMDBChange");
$oChange->Set("date", time());
$oChange->Set("userinfo", "Initialization");
$iChangeId = $oChange->DBInsert();
// Now record the admin user object
$iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */);
$iUserId = $oUser->DBInsertNoReload();
$this->SetupUser($iUserId, true);
return true;
}

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* UserRightsProfile
* User management Module, basing the right on profiles and a matrix (similar to UserRightsMatrix, but profiles and other decorations have been added)
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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('ADMIN_PROFILE_NAME', 'Administrator');
@@ -179,7 +172,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix'));
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
}
@@ -437,8 +430,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
{
CMDBObject::SetTrackInfo('Initialization');
$oChange = CMDBObject::GetCurrentChange();
$iContactId = 0;
// Support drastic data model changes: no organization class (or not writable)!
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
@@ -446,7 +437,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oOrg = new Organization();
$oOrg->Set('name', 'My Company/Department');
$oOrg->Set('code', 'SOMECODE');
$iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */);
$iOrgId = $oOrg->DBInsertNoReload();
// Support drastic data model changes: no Person class (or not writable)!
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
@@ -463,7 +454,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oContact->Set('phone', '+00 000 000 000');
}
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */);
$iContactId = $oContact->DBInsertNoReload();
}
}
@@ -482,14 +473,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
if (is_object($oAdminProfile))
{
$oUserProfile = new URP_UserProfile();
//$oUserProfile->Set('userid', $iUserId);
$oUserProfile->Set('profileid', $oAdminProfile->GetKey());
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
//$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oSet = DBObjectSet::FromObject($oUserProfile);
$oUser->Set('profile_list', $oSet);
}
$iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */);
$iUserId = $oUser->DBInsertNoReload();
return true;
}

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* UserRightsProfile
* User management Module, basing the right on profiles and a matrix (similar to UserRightsMatrix, but profiles and other decorations have been added)
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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('ADMIN_PROFILE_NAME', 'Administrator');
@@ -321,7 +314,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix'));
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
}
@@ -533,10 +526,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
// Create a change to record the history of the User object
/** @var \CMDBChange $oChange */
$oChange = MetaModel::NewObject("CMDBChange");
$oChange->Set("date", time());
$oChange->Set("userinfo", "Initialization");
$iChangeId = $oChange->DBInsert();
$iContactId = 0;
// Support drastic data model changes: no organization class (or not writable)!
@@ -545,7 +538,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oOrg = new Organization();
$oOrg->Set('name', 'My Company/Department');
$oOrg->Set('code', 'SOMECODE');
$iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oOrg::SetCurrentChange($oChange);
$iOrgId = $oOrg->DBInsertNoReload();
// Support drastic data model changes: no Person class (or not writable)!
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
@@ -562,7 +556,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oContact->Set('phone', '+00 000 000 000');
}
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oContact::SetCurrentChange($oChange);
$iContactId = $oContact->DBInsertNoReload();
}
}
@@ -581,14 +576,13 @@ class UserRightsProfile extends UserRightsAddOnAPI
if (is_object($oAdminProfile))
{
$oUserProfile = new URP_UserProfile();
//$oUserProfile->Set('userid', $iUserId);
$oUserProfile->Set('profileid', $oAdminProfile->GetKey());
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
//$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oSet = DBObjectSet::FromObject($oUserProfile);
$oUser->Set('profile_list', $oSet);
}
$iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oUser::SetCurrentChange($oChange);
$iUserId = $oUser->DBInsertNoReload();
return true;
}

View File

@@ -1,27 +1,20 @@
<?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/>
/**
* UserRightsProjection
* User management Module, basing the right on profiles and a matrix (similar to UserRightsProfile, but enhanced with dimensions and projection of classes and profile over the dimensions)
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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('ADMIN_PROFILE_ID', 1);
@@ -153,7 +146,7 @@ class URP_Profiles extends UserRightsBaseClass
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix'));
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
}
@@ -593,25 +586,12 @@ class UserRightsProjection extends UserRightsAddOnAPI
$oChange = MetaModel::NewObject("CMDBChange");
$oChange->Set("date", time());
$oChange->Set("userinfo", "Initialization");
$iChangeId = $oChange->DBInsert();
$oOrg = new Organization();
$oOrg->Set('name', 'My Company/Department');
$oOrg->Set('code', 'SOMECODE');
// $oOrg->Set('status', 'implementation');
//$oOrg->Set('parent_id', xxx);
$iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip strong security */);
// Location : optional
//$oLocation = new bizLocation();
//$oLocation->Set('name', 'MyOffice');
//$oLocation->Set('status', 'implementation');
//$oLocation->Set('org_id', $iOrgId);
//$oLocation->Set('severity', 'high');
//$oLocation->Set('address', 'my building in my city');
//$oLocation->Set('country', 'my country');
//$oLocation->Set('parent_location_id', xxx);
//$iLocationId = $oLocation->DBInsertNoReload();
$oOrg::SetCurrentChange($oChange);
$iOrgId = $oOrg->DBInsertNoReload();
$oContact = new Person();
$oContact->Set('name', 'My last name');
@@ -619,24 +599,24 @@ class UserRightsProjection extends UserRightsAddOnAPI
//$oContact->Set('status', 'available');
$oContact->Set('org_id', $iOrgId);
$oContact->Set('email', 'my.email@foo.org');
//$oContact->Set('phone', '');
//$oContact->Set('location_id', $iLocationId);
//$oContact->Set('employee_number', '');
$iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oContact::SetCurrentChange($oChange);
$iContactId = $oContact->DBInsertNoReload();
$oUser = new UserLocal();
$oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd);
$oUser->Set('contactid', $iContactId);
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
$iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oUser::SetCurrentChange($oChange);
$iUserId = $oUser->DBInsertNoReload();
// Add this user to the very specific 'admin' profile
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('userid', $iUserId);
$oUserProfile->Set('profileid', ADMIN_PROFILE_ID);
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */);
$oUserProfile::SetCurrentChange($oChange);
$oUserProfile->DBInsertNoReload();
return true;
}

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Simple web page with no includes, header or fancy formatting, useful to
* generate HTML fragments when called by an AJAX method
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT."/application/webpage.class.inc.php");
@@ -30,7 +23,7 @@ class ajax_page extends WebPage implements iTabbedPage
{
/**
* Jquery style ready script
* @var Hash
* @var array
*/
protected $m_sReadyScript;
protected $m_oTabs;
@@ -57,56 +50,67 @@ class ajax_page extends WebPage implements iTabbedPage
utils::InitArchiveMode();
}
/**
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
/**
* @inheritDoc
* @throws \Exception
*/
public function AddToTab($sTabContainer, $sTabCode, $sHtml)
{
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabLabel, $sHtml));
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabCode, $sHtml));
}
/**
* @inheritDoc
*/
public function SetCurrentTabContainer($sTabContainer = '')
{
return $this->m_oTabs->SetCurrentTabContainer($sTabContainer);
}
public function SetCurrentTab($sTabLabel = '')
{
return $this->m_oTabs->SetCurrentTab($sTabLabel);
}
/**
* Add a tab which content will be loaded asynchronously via the supplied URL
*
* Limitations:
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
* Static content cannot be added inside such tabs.
*
* @param string $sTabLabel The (localised) label of the tab
* @param string $sUrl The URL to load (on the same server)
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
* @since 2.0.3
* @inheritDoc
*/
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
public function SetCurrentTab($sTabCode = '', $sTabTitle = null)
{
$this->add($this->m_oTabs->AddAjaxTab($sTabLabel, $sUrl, $bCache));
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();
}
public function RemoveTab($sTabLabel, $sTabContainer = null)
/**
* @inheritDoc
*/
public function RemoveTab($sTabCode, $sTabContainer = null)
{
$this->m_oTabs->RemoveTab($sTabLabel, $sTabContainer);
$this->m_oTabs->RemoveTab($sTabCode, $sTabContainer);
}
/**
* Finds the tab whose title matches a given pattern
* @return mixed The name of the tab as a string or false if not found
* @inheritDoc
*/
public function FindTab($sPattern, $sTabContainer = null)
{
@@ -119,21 +123,23 @@ class ajax_page extends WebPage implements iTabbedPage
* that we are using this is not supported... TO DO upgrade
* the whole jquery bundle...
*/
public function SelectTab($sTabContainer, $sTabLabel)
public function SelectTab($sTabContainer, $sTabCode)
{
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabLabel));
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabCode));
}
/**
* @param string $sHtml
*/
public function AddToMenu($sHtml)
{
$this->m_sMenu .= $sHtml;
}
/**
* Echoes the content of the whole page
* @return void
*/
public function output()
/**
* @inheritDoc
*/
public function output()
{
if (!empty($this->sContentType))
{
@@ -216,8 +222,9 @@ EOF
}
$this->outputCollapsibleSectionInit();
$oKPI = new ExecutionKPI();
$s_captured_output = $this->ob_get_clean_safe();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
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);
@@ -287,6 +294,8 @@ EOF
echo self::FilterXSS($s_captured_output);
}
$oKPI->ComputeAndReport('Echoing');
if (class_exists('DBSearch'))
{
DBSearch::RecordQueryTrace();
@@ -307,7 +316,11 @@ EOF
{
}
public function add($sHtml)
/**
* @inheritDoc
* @throws \Exception
*/
public function add($sHtml)
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
{
@@ -320,10 +333,9 @@ EOF
}
/**
* Records the current state of the 'html' part of the page output
* @return mixed The current state of the 'html' output
*/
public function start_capture()
* @inheritDoc
*/
public function start_capture()
{
$sCurrentTabContainer = $this->m_oTabs->GetCurrentTabContainer();
$sCurrentTab = $this->m_oTabs->GetCurrentTab();
@@ -339,13 +351,10 @@ EOF
}
}
/**
* Returns the part of the html output that occurred since the call to start_capture
* and removes this part from the current html output
* @param $offset mixed The value returned by start_capture
* @return string The part of the html output that was added since the call to start_capture
*/
public function end_capture($offset)
/**
* @inheritDoc
*/
public function end_capture($offset)
{
if (is_array($offset))
{
@@ -366,11 +375,9 @@ EOF
}
/**
* Add any text or HTML fragment (identified by an ID) at the end of the body of the page
* This is useful to add hidden content, DIVs or FORMs that should not
* be embedded into each other.
* @inheritDoc
*/
public function add_at_the_end($s_html, $sId = '')
public function add_at_the_end($s_html, $sId = '')
{
if ($sId != '')
{
@@ -378,27 +385,27 @@ EOF
}
$this->s_deferred_content .= $s_html;
}
/**
* Adds a script to be executed when the DOM is ready (typical JQuery use)
* NOT implemented in this version of the class.
* @return void
*/
* @inheritDoc
*/
public function add_ready_script($sScript)
{
$this->m_sReadyScript .= $sScript."\n";
}
/**
* Cannot be called in this context, since Ajax pages do not share
* any context with the calling page !!
* @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

@@ -1,20 +1,24 @@
<?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/>
/**
* 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
*/
use Symfony\Component\DependencyInjection\Container;
require_once(APPROOT.'application/newsroomprovider.class.inc.php');
@@ -28,6 +32,7 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @package Extensibility
* @since 2.7.0
*/
interface iLoginExtension
{
@@ -39,6 +44,9 @@ interface iLoginExtension
public function ListSupportedLoginModes();
}
/**
* @since 2.7.0
*/
interface iLoginFSMExtension extends iLoginExtension
{
/**
@@ -56,8 +64,14 @@ interface iLoginFSMExtension extends iLoginExtension
public function LoginAction($sLoginState, &$iErrorCode);
}
/**
* @since 2.7.0
*/
abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
{
/**
* @inheritDoc
*/
public abstract function ListSupportedLoginModes();
/**
@@ -149,27 +163,50 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
*/
protected function OnCredentialsOK(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
*/
protected function OnUsersOK(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
*/
protected function OnConnected(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
*
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
*/
protected function OnError(&$iErrorCode)
{
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
}
/**
* @since 2.7.0
*/
interface iLogoutExtension extends iLoginExtension
{
/**
@@ -178,15 +215,22 @@ interface iLogoutExtension extends iLoginExtension
public function LogoutAction();
}
interface iLoginDataExtension extends iLoginExtension
/**
* @since 2.7.0
*/
interface iLoginUIExtension extends iLoginExtension
{
/**
* @return LoginTwigData
* @return LoginTwigContext
*/
public function GetLoginData();
public function GetTwigContext();
}
/**
* @api
* @package Extensibility
* @since 2.7.0
*/
interface iPreferencesExtension
{
/**
@@ -204,6 +248,33 @@ interface iPreferencesExtension
public function ApplyPreferences(WebPage $oPage, $sOperation);
}
/**
* Extend this class instead of implementing iPreferencesExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @since 2.7.0
*/
abstract class AbstractPreferencesExtension implements iPreferencesExtension
{
/**
* @inheritDoc
*/
public function DisplayPreferences(WebPage $oPage)
{
// Do nothing
}
/**
* @inheritDoc
*/
public function ApplyPreferences(WebPage $oPage, $sOperation)
{
// Do nothing
}
}
/**
* Implement this interface to change the behavior of the GUI for some objects.
*
@@ -364,6 +435,77 @@ interface iApplicationUIExtension
public function EnumAllowedActions(DBObjectSet $oSet);
}
/**
* Extend this class instead of implementing iApplicationUIExtension if you don't need to overload
*
* @api
* @package Extensibility
* @since 2.7.0
*/
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
{
/**
* @inheritDoc
*/
public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false)
{
}
/**
* @inheritDoc
*/
public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false)
{
}
/**
* @inheritDoc
*/
public function OnFormSubmit($oObject, $sFormPrefix = '')
{
}
/**
* @inheritDoc
*/
public function OnFormCancel($sTempId)
{
}
/**
* @inheritDoc
*/
public function EnumUsedAttributes($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function GetIcon($oObject)
{
return '';
}
/**
* @inheritDoc
*/
public function GetHilightClass($oObject)
{
return HILIGHT_CLASS_NONE;
}
/**
* @inheritDoc
*/
public function EnumAllowedActions(DBObjectSet $oSet)
{
return array();
}
}
/**
* Implement this interface to perform specific things when objects are manipulated
*
@@ -391,7 +533,7 @@ interface iApplicationObjectExtension
public function OnIsModified($oObject);
/**
* Invoked to determine wether an object can be written to the database
* Invoked to determine whether an object can be written to the database
*
* The GUI calls this verb and reports any issue.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
@@ -419,7 +561,10 @@ interface iApplicationObjectExtension
* Invoked when an object is updated into the database. The method is called right <b>after</b> the object has been written to the
* database.
*
* Changes made to the object can be get using {@link \cmdbAbstractObject::$m_aChanges}
* Useful methods you can call on $oObject :
*
* * {@see DBObject::ListPreviousValuesForUpdatedAttributes()} : list of changed attributes and their values before the change
* * {@see DBObject::Get()} : for a given attribute the new value that was persisted
*
* @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
@@ -427,7 +572,7 @@ interface iApplicationObjectExtension
*
* @return void
*
* @since 2.7.0 N°2293 can access object changes by calling {@link \cmdbAbstractObject::$m_aChanges}
* @since 2.7.0 N°2293 can access object changes by calling {@see DBObject::ListPreviousValuesForUpdatedAttributes()} on $oObject
*/
public function OnDBUpdate($oObject, $oChange = null);
@@ -458,6 +603,62 @@ interface iApplicationObjectExtension
public function OnDBDelete($oObject, $oChange = null);
}
/**
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @since 2.7.0
*/
abstract class AbstractApplicationObjectExtension implements iApplicationObjectExtension
{
/**
* @inheritDoc
*/
public function OnIsModified($oObject)
{
return false;
}
/**
* @inheritDoc
*/
public function OnCheckToWrite($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function OnCheckToDelete($oObject)
{
return array();
}
/**
* @inheritDoc
*/
public function OnDBUpdate($oObject, $oChange = null)
{
}
/**
* @inheritDoc
*/
public function OnDBInsert($oObject, $oChange = null)
{
}
/**
* @inheritDoc
*/
public function OnDBDelete($oObject, $oChange = null)
{
}
}
/**
* New extension to add menu items in the "popup" menus inside iTop. Provides a greater flexibility than
* iApplicationUIExtension::EnumAllowedActions.
@@ -584,7 +785,6 @@ abstract class 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 array $aCssClasses The CSS classes to add to the menu
*/
public function __construct($sUID, $sLabel)
{
@@ -846,6 +1046,41 @@ interface iPageUIExtension
public function GetBannerHtml(iTopWebPage $oPage);
}
/**
* Extend this class instead of iPageUIExtension if you don't need to overload all methods
*
* @api
* @package Extensibility
* @since 2.7.0
*/
abstract class AbstractPageUIExtension implements iPageUIExtension
{
/**
* @inheritDoc
*/
public function GetNorthPaneHtml(iTopWebPage $oPage)
{
return '';
}
/**
* @inheritDoc
*/
public function GetSouthPaneHtml(iTopWebPage $oPage)
{
return '';
}
/**
* @inheritDoc
*/
public function GetBannerHtml(iTopWebPage $oPage)
{
return '';
}
}
/**
* Implement this interface to add content to any enhanced portal page
*
@@ -853,7 +1088,7 @@ interface iPageUIExtension
*
* @api
* @package Extensibility
* @since 2.4
* @since 2.4.0
*/
interface iPortalUIExtension
{
@@ -864,65 +1099,65 @@ interface iPortalUIExtension
/**
* Returns an array of CSS file urls
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
*/
public function GetCSSFiles(\Silex\Application $oApp);
public function GetCSSFiles(Container $oContainer);
/**
* Returns inline (raw) CSS
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
*/
public function GetCSSInline(\Silex\Application $oApp);
public function GetCSSInline(Container $oContainer);
/**
* Returns an array of JS file urls
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return array
*/
public function GetJSFiles(\Silex\Application $oApp);
public function GetJSFiles(Container $oContainer);
/**
* Returns raw JS code
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
*/
public function GetJSInline(\Silex\Application $oApp);
public function GetJSInline(Container $oContainer);
/**
* Returns raw HTML code to put at the end of the <body> tag
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
*/
public function GetBodyHTML(\Silex\Application $oApp);
public function GetBodyHTML(Container $oContainer);
/**
* Returns raw HTML code to put at the end of the #main-wrapper element
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
*/
public function GetMainContentHTML(\Silex\Application $oApp);
public function GetMainContentHTML(Container $oContainer);
/**
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
*
* @param \Silex\Application $oApp
* @param \Symfony\Component\DependencyInjection\Container $oContainer
*
* @return string
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp);
public function GetNavigationMenuHTML(Container $oContainer);
}
/**
@@ -933,7 +1168,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetCSSFiles(\Silex\Application $oApp)
public function GetCSSFiles(Container $oContainer)
{
return array();
}
@@ -941,7 +1176,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetCSSInline(\Silex\Application $oApp)
public function GetCSSInline(Container $oContainer)
{
return null;
}
@@ -949,7 +1184,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetJSFiles(\Silex\Application $oApp)
public function GetJSFiles(Container $oContainer)
{
return array();
}
@@ -957,7 +1192,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetJSInline(\Silex\Application $oApp)
public function GetJSInline(Container $oContainer)
{
return null;
}
@@ -965,7 +1200,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetBodyHTML(\Silex\Application $oApp)
public function GetBodyHTML(Container $oContainer)
{
return null;
}
@@ -973,7 +1208,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetMainContentHTML(\Silex\Application $oApp)
public function GetMainContentHTML(Container $oContainer)
{
return null;
}
@@ -981,7 +1216,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
/**
* @inheritDoc
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp)
public function GetNavigationMenuHTML(Container $oContainer)
{
return null;
}
@@ -1077,11 +1312,6 @@ class RestResult
/**
* Default constructor - ok!
*
* @param DBObject $oObject The object being reported
* @param string $sAttCode The attribute code (must be valid)
*
* @return string A scalar representation of the value
*/
public function __construct()
{
@@ -1528,3 +1758,16 @@ class RestUtils
return $oObject;
}
}
/**
* Helpers for modules extensibility, with discover performed by the MetaModel.
*
*
* @api
* @package Extensibility
*/
interface iModuleExtension
{
public function __construct();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,21 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* 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
*/
require_once(APPROOT.'application/dashboardlayout.class.inc.php');
require_once(APPROOT.'application/dashlet.class.inc.php');
@@ -24,21 +25,33 @@ require_once(APPROOT.'core/modelreflection.class.inc.php');
*
* A user editable dashboard page
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class Dashboard
{
/** @var string $sTitle*/
protected $sTitle;
/** @var bool $bAutoReload */
protected $bAutoReload;
/** @var float|int $iAutoReloadSec */
protected $iAutoReloadSec;
/** @var string $sLayoutClass */
protected $sLayoutClass;
/** @var array $aWidgetsData */
protected $aWidgetsData;
/** @var \DOMNode|null $oDOMNode */
protected $oDOMNode;
/** @var string $sId */
protected $sId;
/** @var array $aCells */
protected $aCells;
/** @var \ModelReflection $oMetaModel */
protected $oMetaModel;
/**
* Dashboard constructor.
*
* @param string $sId
*/
public function __construct($sId)
{
$this->sTitle = '';
@@ -51,7 +64,7 @@ abstract class Dashboard
}
/**
* @param $sXml
* @param string $sXml
*
* @throws \Exception
*/
@@ -64,7 +77,10 @@ abstract class Dashboard
restore_error_handler();
$this->FromDOMDocument($oDoc);
}
/**
* @param \DOMDocument $oDoc
*/
public function FromDOMDocument(DOMDocument $oDoc)
{
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
@@ -160,9 +176,10 @@ abstract class Dashboard
protected function InitDashletFromDOMNode($oDomNode)
{
$sId = $oDomNode->getAttribute('id');
$sDashletType = $oDomNode->getAttribute('xsi:type');
// Test if dashlet can be instanciated, otherwise (uninstalled, broken, ...) we display a placeholder
// Test if dashlet can be instantiated, otherwise (uninstalled, broken, ...) we display a placeholder
$sClass = static::GetDashletClassFromType($sDashletType);
/** @var \Dashlet $oNewDashlet */
$oNewDashlet = new $sClass($this->oMetaModel, $sId);
@@ -172,7 +189,13 @@ abstract class Dashboard
return $oNewDashlet;
}
static function SortOnRank($aItem1, $aItem2)
/**
* @param array $aItem1
* @param array $aItem2
*
* @return int
*/
public static function SortOnRank($aItem1, $aItem2)
{
return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1;
}
@@ -200,6 +223,10 @@ abstract class Dashboard
}
}
/**
* @return string
* @throws \Exception
*/
public function ToXml()
{
$oDoc = new DOMDocument();
@@ -268,7 +295,9 @@ abstract class Dashboard
}
}
/**
* @param array $aParams
*/
public function FromParams($aParams)
{
$this->sLayoutClass = $aParams['layout_class'];
@@ -305,42 +334,85 @@ abstract class Dashboard
{
}
/**
* @return mixed
*/
public function GetId()
{
return $this->sId;
}
/**
* Return a sanitize ID for usages in XML/HTML attributes
*
* @return string
* @since 2.7.0
*/
public function GetSanitizedId()
{
return utils::Sanitize($this->GetId(), '', 'element_identifier');
}
/**
* @return string
*/
public function GetLayout()
{
return $this->sLayoutClass;
}
/**
* @param string $sLayoutClass
*/
public function SetLayout($sLayoutClass)
{
$this->sLayoutClass = $sLayoutClass;
}
/**
* @return string
*/
public function GetTitle()
{
return $this->sTitle;
}
/**
* @param string $sTitle
*/
public function SetTitle($sTitle)
{
$this->sTitle = $sTitle;
}
/**
* @return bool
*/
public function GetAutoReload()
{
return $this->bAutoReload;
}
/**
* @param bool $bAutoReload
*/
public function SetAutoReload($bAutoReload)
{
$this->bAutoReload = $bAutoReload;
}
/**
* @return float|int
*/
public function GetAutoReloadInterval()
{
return $this->iAutoReloadSec;
}
/**
* @param bool $iAutoReloadSec
*/
public function SetAutoReloadInterval($iAutoReloadSec)
{
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$iAutoReloadSec);
@@ -356,12 +428,13 @@ abstract class Dashboard
$this->aCells[] = array($oDashlet);
}
/**
* @param \WebPage $oPage *
* @param array $aExtraParams
*
* @throws \ReflectionException
*/
/**
* @param \WebPage $oPage *
* @param array $aExtraParams
*
* @throws \ReflectionException
* @throws \Exception
*/
public function RenderProperties($oPage, $aExtraParams = array())
{
// menu to pick a layout and edit other properties of the dashboard
@@ -449,17 +522,32 @@ EOF
}
/**
* @param \iTopWebPage $oPage
* @param \WebPage $oPage
* @param bool $bEditMode
* @param array $aExtraParams
* @param bool $bCanEdit
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{
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>');
$oLayout = new $this->sLayoutClass;
/** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass();
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)
{
@@ -468,6 +556,12 @@ EOF
}
}
/**
* @param \WebPage $oPage
*
* @throws \ReflectionException
* @throws \Exception
*/
public function RenderDashletsSelection(WebPage $oPage)
{
// Toolbox/palette to drag and drop dashlets
@@ -485,25 +579,31 @@ EOF
$oPage->add('</div>');
$oPage->add_ready_script("$('.dashlet_icon').draggable({helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});");
}
/**
* @param \WebPage $oPage
* @param array $aExtraParams
*/
public function RenderDashletsProperties(WebPage $oPage, $aExtraParams = array())
{
// Toolbox/palette to edit the properties of each dashlet
$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:DashletProperties').'</div>');
/** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass();
$oPage->add('<div id="dashlet_properties" style="text-align:center">');
foreach($this->aCells as $aCell)
foreach($this->aCells as $iCellIdx => $aCell)
{
/** @var \Dashlet $oDashlet */
foreach($aCell as $oDashlet)
{
$sId = $oDashlet->GetID();
if ($oDashlet->IsVisible())
{
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" style="display:none">');
$oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm, $aExtraParams);
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
}
}
@@ -545,7 +645,10 @@ EOF
return $aDashlets;
}
/**
* @return int|mixed
*/
protected function GetNewDashletId()
{
$iNewId = 0;
@@ -560,14 +663,32 @@ EOF
return $iNewId + 1;
}
/**
* Prepare dashlet for rendering (eg. change its ID or another processing).
* Meant to be overloaded.
*
* @param \Dashlet $oDashlet
* @param array $aCoordinates
* @param array $aExtraParams
*
* @return void
*/
abstract protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = array());
/**
* @param $oForm
* @param \DesignerForm $oForm
* @param array $aExtraParams
*
* @return mixed
*/
abstract protected function SetFormParams($oForm, $aExtraParams = array());
/**
* @param string $sType
* @param \ModelFactory|null $oFactory
*
* @return string
*/
public static function GetDashletClassFromType($sType, $oFactory = null)
{
if (is_subclass_of($sType, 'Dashlet'))
@@ -578,45 +699,90 @@ EOF
}
/**
* @return mixed
* N°2634: we must have a unique id per dashlet!
* To avoid collision with other dashlets with the same ID we prefix it with row/cell id
* Collisions typically happen with extensions.
*
* @param boolean $bIsCustomized
* @param string $sDashboardDivId
* @param int $iRow
* @param int $iCol
* @param string $sDashletOrigId
*
* @return string
*
* @since 2.7.0 N°2735
*/
public function GetId()
public static function GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletOrigId)
{
return $this->sId;
if(strpos($sDashletOrigId, '_ID_row') !== false)
{
return $sDashletOrigId;
}
$sDashletId = $sDashboardDivId."_ID_row".$iRow."_col".$iCol."_".$sDashletOrigId;
if ($bIsCustomized)
{
$sDashletId = 'CUSTOM_'.$sDashletId;
}
return $sDashletId;
}
}
/**
* Class RuntimeDashboard
*/
class RuntimeDashboard extends Dashboard
{
protected $bCustomized;
/** @var string $sDefinitionFile */
private $sDefinitionFile = '';
/** @var null $sReloadURL */
private $sReloadURL = null;
/** @var bool $bCustomized */
protected $bCustomized;
/**
* @inheritDoc
*/
public function __construct($sId)
{
parent::__construct($sId);
$this->bCustomized = false;
$this->oMetaModel = new ModelReflectionRuntime();
$this->bCustomized = false;
}
/**
* @return bool
* @since 2.7.0
*/
public function GetCustomFlag()
{
return $this->bCustomized;
}
/**
* @param bool $bCustomized
* @since 2.7.0
*/
public function SetCustomFlag($bCustomized)
{
$this->bCustomized = $bCustomized;
}
/**
* @param \DesignerForm $oForm
*
* @param array $aExtraParams
*
* @inheritDoc
* @throws \Exception
*/
protected function SetFormParams($oForm, $aExtraParams = array())
{
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
}
/**
* @inheritDoc
* @throws \Exception
*/
public function Save()
{
$sXml = $this->ToXml();
@@ -632,7 +798,7 @@ class RuntimeDashboard extends Dashboard
}
else
{
// No such customized dasboard for the current user, let's create a new record
// No such customized dashboard for the current user, let's create a new record
$oUserDashboard = new UserDashboard();
$oUserDashboard->Set('user_id', UserRights::GetUserId());
$oUserDashboard->Set('menu_code', $this->sId);
@@ -642,7 +808,18 @@ class RuntimeDashboard extends Dashboard
$oUserDashboard->DBWrite();
utils::PopArchiveMode();
}
/**
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function Revert()
{
$oUDSearch = new DBObjectSearch('UserDashboard');
@@ -669,6 +846,7 @@ class RuntimeDashboard extends Dashboard
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
public static function GetDashboard($sDashboardFile, $sDashBoardId)
{
@@ -713,10 +891,7 @@ class RuntimeDashboard extends Dashboard
}
/**
* @param \iTopWebPage $oPage
* @param bool $bEditMode
* @param array $aExtraParams (class and id of the current object
*
* @inheritDoc
* @throws \Exception
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
@@ -746,7 +921,7 @@ class RuntimeDashboard extends Dashboard
if (!$bEditMode && !$oPage->IsPrintableVersion())
{
$sId = $this->GetId();
$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sId);
$sDivId = utils::Sanitize($sId, '', 'element_identifier');
if ($this->GetAutoReload())
{
$sFile = addslashes($this->GetDefinitionFile());
@@ -809,7 +984,7 @@ EOF
protected function RenderSelector($oPage, $aAjaxParams = array())
{
$sId = $this->GetId();
$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sId);
$sDivId = utils::Sanitize($sId, '', 'element_identifier');
$sExtraParams = json_encode($aAjaxParams);
$sSelectorHtml = '<div class="dashboard-selector">';
@@ -851,6 +1026,9 @@ EOF
);
}
/**
* @return bool
*/
protected function HasCustomDashboard()
{
try
@@ -879,7 +1057,7 @@ EOF
{
$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><img src=\"../images/pencil-menu.png\"><ul>";
$sEditMenu = "<div id=\"DashboardMenu\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-pencil-alt\"></i><ul>";
$aActions = array();
$sFile = addslashes($this->sDefinitionFile);
@@ -939,9 +1117,7 @@ EOF
}
/**
* @param \WebPage $oPage
*
* @throws \ReflectionException
* @inheritDoc
*/
public function RenderProperties($oPage, $aExtraParams = array())
{
@@ -977,7 +1153,7 @@ EOF
/**
* @param \iTopWebPage $oPage
* @param \WebPage $oPage
*
* @param array $aExtraParams
*
@@ -997,9 +1173,11 @@ EOF
{
$aRenderParams = $aExtraParams;
}
$aRenderParams['dashboard_div_id'] = $aExtraParams['dashboard_div_id'];
$sJSExtraParams = json_encode($aExtraParams);
$oPage->add('<div id="dashboard_editor">');
$oPage->add('<div class="ui-layout-center">');
$this->SetCustomFlag(true);
$this->Render($oPage, true, $aRenderParams);
$oPage->add('</div>');
$oPage->add('<div class="ui-layout-east">');
@@ -1028,7 +1206,7 @@ EOF
$sAutoApplyConfirmationMessage = addslashes(Dict::S('UI:AutoApplyConfirmationMessage'));
$oPage->add_ready_script(
<<<EOF
<<<JS
window.bLeavingOnUserAction = false;
$('#dashboard_editor').dialog({
@@ -1071,10 +1249,15 @@ $('#dashboard_editor').dialog({
});
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId', layout_class: '$sLayoutClass', title: '$sTitle',
auto_reload: $sAutoReload, auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl', submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_to: '$sUrl', render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
dashboard_id: '$sId',
layout_class: '$sLayoutClass',
title: '$sTitle',
auto_reload: $sAutoReload,
auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl',
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_to: '$sUrl',
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});
@@ -1113,11 +1296,18 @@ window.onbeforeunload = function() {
}
// return nothing ! safer for IE
};
EOF
JS
);
$oPage->add_ready_script("");
}
/**
* @param string|null $sOQL
*
* @return \DesignerForm
* @throws \DictExceptionMissingString
* @throws \ReflectionException
*/
public static function GetDashletCreationForm($sOQL = null)
{
$oAppContext = new ApplicationContext();
@@ -1228,6 +1418,9 @@ EOF
/**
* @param \WebPage $oPage
* @param $sOQL
*
* @throws \DictExceptionMissingString
* @throws \ReflectionException
*/
public static function GetDashletCreationDlgFromOQL($oPage, $sOQL)
{
@@ -1243,7 +1436,7 @@ EOF
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$oPage->add_ready_script(
<<<EOF
<<<JS
$('#dashlet_creation_dlg').dialog({
width: 600,
modal: true,
@@ -1272,7 +1465,7 @@ $('#dashlet_creation_dlg').dialog({
],
close: function() { $(this).remove(); }
});
EOF
JS
);
}
@@ -1292,13 +1485,98 @@ EOF
$this->sDefinitionFile = $sDefinitionFile;
}
/**
* @return string|null
*/
public function GetReloadURL()
{
return $this->sReloadURL;
}
/**
* @param string $sReloadURL
*/
public function SetReloadURL($sReloadURL)
{
$this->sReloadURL = $sReloadURL;
}
}
/**
* @inheritDoc
*/
protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = array())
{
$sDashletIdOrig = $oDashlet->GetID();
$sDashboardSanitizedId = $this->GetSanitizedId();
$sDashletIdNew = static::GetDashletUniqueId($this->GetCustomFlag(), $sDashboardSanitizedId, $aCoordinates[1], $aCoordinates[0], $sDashletIdOrig);
$oDashlet->SetID($sDashletIdNew);
$this->UpdateDashletUserPrefs($oDashlet, $sDashletIdOrig, $aExtraParams);
}
/**
* Migrate dashlet specific prefs to new format
* Before 2.7.0 we were using the same for dashboard menu or dashboard attributes, standard or custom :
* <alias>-<class>|Dashlet<idx_dashlet>
* Since 2.7.0 it is the following, with a "CUSTOM_" prefix if necessary :
* * dashboard menu : <dashboard_id>_IDrow<row_idx>-col<col_idx>-<dashlet_idx>
* * dashboard attribute : <class>__<attcode>_IDrow<row_idx>-col<col_idx>-<dashlet_idx>
*
* @param \Dashlet $oDashlet
* @param string $sDashletIdOrig
*
* @param array $aExtraParams
*
* @since 2.7.0 N°2735
*/
private function UpdateDashletUserPrefs(Dashlet $oDashlet, $sDashletIdOrig, array $aExtraParams)
{
$bIsDashletWithListPref = ($oDashlet instanceof DashletObjectList);
if (!$bIsDashletWithListPref)
{
return;
}
/** @var \DashletObjectList $oDashlet */
$bDashletIdInNewFormat = ($sDashletIdOrig === $oDashlet->GetID());
if ($bDashletIdInNewFormat)
{
return;
}
$sNewPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $oDashlet->GetID());
$sPrefValueForNewKey = appUserPreferences::GetPref($sNewPrefKey, null);
$bHasPrefInNewFormat = ($sPrefValueForNewKey !== null);
if ($bHasPrefInNewFormat)
{
return;
}
$sOldPrefKey = $this->GetDashletObjectListAppUserPreferencesPrefix($oDashlet, $aExtraParams, $sDashletIdOrig);
$sPrefValueForOldKey = appUserPreferences::GetPref($sOldPrefKey, null);
$bHasPrefInOldFormat = ($sPrefValueForOldKey !== null);
if (!$bHasPrefInOldFormat)
{
return;
}
appUserPreferences::SetPref($sNewPrefKey, $sPrefValueForOldKey);
appUserPreferences::UnsetPref($sOldPrefKey);
}
/**
* @param \DashletObjectList $oDashlet
* @param array $aExtraParams
* @param string $sDashletId
*
* @return string
* @since 2.7.0
*/
private function GetDashletObjectListAppUserPreferencesPrefix(DashletObjectList $oDashlet, $aExtraParams, $sDashletId)
{
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
$oFilter = $oDashlet->GetDBSearch($aExtraParams);
$aClassAliases = $oFilter->GetSelectedClasses();
return DataTableSettings::GetAppUserPreferenceKey($aClassAliases, $sDataTableId);
}
}

View File

@@ -26,14 +26,17 @@
abstract class DashboardLayout
{
public function __construct()
{
}
abstract public function Render($oPage, $aDashlets, $bEditMode = false);
/**
* @param int $iCellIdx
*
* @return array Containing 2 scalars: Col number and row number (starting from 0)
* @since 2.7.0
*/
abstract public function GetDashletCoordinates($iCellIdx);
static public function GetInfo()
public static function GetInfo()
{
return array(
'label' => '',
@@ -51,7 +54,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
{
$this->iNbCols = 1;
}
protected function TrimCell($aDashlets)
{
$aKeys = array_reverse(array_keys($aDashlets));
@@ -61,7 +64,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
{
/** @var \Dashlet $oDashlet */
$oDashlet = $aDashlets[$aKeys[$idx]];
if ($oDashlet->IsVisible())
if ($oDashlet::IsVisible())
{
$bNoVisibleFound = false;
}
@@ -110,20 +113,21 @@ 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>');
$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>');
$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-cell-index=\"$iCellIdx\">");
$oPage->add("<td style=\"$sStyle\" class=\"$sCellClass\" data-dashboard-column-index=\"$iCols\" data-dashboard-cell-index=\"$iCellIdx\">");
if (array_key_exists($iCellIdx, $aCells))
{
$aDashlets = $aCells[$iCellIdx];
@@ -132,7 +136,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
/** @var \Dashlet $oDashlet */
foreach($aDashlets as $oDashlet)
{
if ($oDashlet->IsVisible())
if ($oDashlet::IsVisible())
{
$oDashlet->DoRender($oPage, $bEditMode, true /* bEnclosingDiv */, $aExtraParams);
}
@@ -155,10 +159,10 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
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>');
$oPage->add("<tr data-dashboard-row-index=\"$iRows\">");
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle>");
$oPage->add("<td $sStyle data-dashboard-column-index=\"$iCols\">");
$oPage->add('&nbsp;');
$oPage->add('</td>');
}
@@ -166,6 +170,17 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
}
$oPage->add('</tbody></table>');
}
/**
* @inheritDoc
*/
public function GetDashletCoordinates($iCellIdx)
{
$iColNumber = (int) $iCellIdx % $this->iNbCols;
$iRowNumber = (int) floor($iCellIdx / $this->iNbCols);
return array($iColNumber, $iRowNumber);
}
}
class DashboardLayoutOneCol extends DashboardLayoutMultiCol

View File

@@ -26,6 +26,9 @@ require_once(APPROOT.'application/forms.class.inc.php');
*/
abstract class Dashlet
{
/** @var string */
const APPUSERPREFERENCES_PREFIX = 'Dashlet';
protected $oModelReflection;
protected $sId;
protected $bRedrawNeeded;
@@ -514,7 +517,7 @@ EOF
*
* Used as a fallback in iTop for unknown dashlet classes.
*
* @since 2.5
* @since 2.5.0
*/
class DashletUnknown extends Dashlet
{
@@ -613,12 +616,12 @@ class DashletUnknown extends Dashlet
{
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$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">');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
@@ -633,12 +636,12 @@ class DashletUnknown extends Dashlet
{
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$sIconUrl = utils::HtmlEntities(utils::GetAbsoluteUrlAppRoot().$aInfos['icon']);
$sExplainText = Dict::Format('UI:DashletUnknown:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
@@ -774,12 +777,12 @@ class DashletProxy extends DashletUnknown
{
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$sIconUrl = utils::HtmlEntities(utils::GetAbsoluteUrlAppRoot().$aInfos['icon']);
$sExplainText = Dict::Format('UI:DashletProxy:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="dashlet-pxy-image"><img src="'.utils::HtmlEntities($sIconUrl).'" /></div>');
$oPage->add('<div class="dashlet-pxy-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-pxy-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
@@ -860,7 +863,7 @@ class DashletPlainText extends Dashlet
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sText = htmlentities($this->aProperties['text'], ENT_QUOTES, 'UTF-8');
$sText = utils::HtmlEntities($this->aProperties['text']);
$sText = str_replace(array("\r\n", "\n", "\r"), "<br/>", $sText);
$sId = 'plaintext_'.($bEditMode? 'edit_' : '').$this->sId;
@@ -913,15 +916,28 @@ class DashletObjectList extends Dashlet
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sQuery = $this->aProperties['query'];
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
$oPage->add('<div class="dashlet-content">');
$sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
if ($sHtmlTitle != '')
{
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
}
$oFilter = $this->GetDBSearch($aExtraParams);
$oBlock = new DisplayBlock($oFilter, 'list');
$aParams = array(
'menu' => $sShowMenu,
'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>');
}
public function GetDBSearch($aExtraParams = array())
{
$sQuery = $this->aProperties['query'];
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
@@ -935,15 +951,8 @@ class DashletObjectList extends Dashlet
{
$aQueryParams = array();
}
$oFilter = DBObjectSearch::FromOQL($sQuery, $aQueryParams);
$oBlock = new DisplayBlock($oFilter, 'list');
$aParams = array(
'menu' => $sShowMenu,
'table_id' => 'Dashlet'.$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>');
return DBObjectSearch::FromOQL($sQuery, $aQueryParams);
}
/**
@@ -956,7 +965,7 @@ class DashletObjectList extends Dashlet
$bShowMenu = $this->aProperties['menu'];
$oPage->add('<div class="dashlet-content">');
$sHtmlTitle = htmlentities($this->oModelReflection->DictString($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
$sHtmlTitle = utils::HtmlEntities($this->oModelReflection->DictString($sTitle)); // done in the itop block
if ($sHtmlTitle != '')
{
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
@@ -1249,7 +1258,7 @@ abstract class DashletGroupBy extends Dashlet
case 'table':
default:
$sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
$sType = 'count';
$aParams = array(
'group_by' => $this->sGroupByExpr,
@@ -1686,7 +1695,7 @@ class DashletGroupByPie extends DashletGroupBy
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
$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>");
$aDisplayValues = $this->MakeSimulatedData();
@@ -1758,7 +1767,7 @@ class DashletGroupByBars extends DashletGroupBy
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
$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>");
$aDisplayValues = $this->MakeSimulatedData();
@@ -1907,16 +1916,16 @@ class DashletHeaderStatic extends Dashlet
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sTitle = utils::HtmlEntities($this->aProperties['title']);
$sIcon = $this->aProperties['icon'];
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
$oPage->add('<img src="'.$sIconPath.'">');
$oPage->add('<h1>'.$this->oModelReflection->DictString($sTitle).'</h1>');
$oPage->add('</div>');
@@ -2037,14 +2046,14 @@ class DashletHeaderDynamic extends Dashlet
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sTitle = utils::HtmlEntities($this->aProperties['title']);
$sIcon = $this->aProperties['icon'];
$sSubtitle = $this->aProperties['subtitle'];
$sSubtitle = utils::HtmlEntities($this->aProperties['subtitle']);
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$aValues = $this->GetValues();
if (count($aValues) > 0)
@@ -2072,7 +2081,7 @@ class DashletHeaderDynamic extends Dashlet
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
$oPage->add('<img src="'.$sIconPath.'">');
if (isset($aExtraParams['query_params']))
{
@@ -2101,9 +2110,9 @@ class DashletHeaderDynamic extends Dashlet
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sTitle = utils::HtmlEntities($this->aProperties['title']);
$sIcon = $this->aProperties['icon'];
$sSubtitle = $this->aProperties['subtitle'];
$sSubtitle = utils::HtmlEntities($this->aProperties['subtitle']);
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
@@ -2111,12 +2120,12 @@ class DashletHeaderDynamic extends Dashlet
$sClass = $oQuery->GetClass();
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.utils::HtmlEntities($sIconPath).'">');
$oPage->add('<img src="'.$sIconPath.'">');
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
@@ -2147,8 +2156,8 @@ class DashletHeaderDynamic extends Dashlet
$sTitle = $this->oModelReflection->DictString($sTitle);
$sSubtitle = $this->oModelReflection->DictFormat($sSubtitle, $iTotal);
$oPage->add('<h1>'.$sTitle.'</h1>');
$oPage->add('<a class="summary">'.$sSubtitle.'</a>');
$oPage->add('<h1>'.utils::HtmlEntities($sTitle).'</h1>');
$oPage->add('<a class="summary">'.utils::HtmlEntities($sSubtitle).'</a>');
$oPage->add('</div>');
$oPage->add('</div>');

View File

@@ -15,5 +15,10 @@
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
<rank>80</rank>
</menu>
<menu id="SystemTools" xsi:type="MenuGroup" _delta="define">
<rank>100</rank>
<enable_class>ResourceSystemMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
</menus>
</itop_design>

View File

@@ -1,25 +1,20 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Data Table to display a set of objects in a tabular manner in HTML
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
class DataTable
@@ -36,8 +31,13 @@ class DataTable
/**
* @param $iListId mixed Unique ID for this div/table in the page
* @param $oSet DBObjectSet The set of data to display
* @param $aClassAliases Hash The list of classes/aliases to be displayed in this set $sAlias => $sClassName
* @param $aClassAliases array The list of classes/aliases to be displayed in this set $sAlias => $sClassName
* @param $sTableId mixed A string (or null) identifying this table in order to persist its settings
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function __construct($iListId, $oSet, $aClassAliases, $sTableId = null)
{
@@ -50,7 +50,19 @@ class DataTable
$this->oDefaultSettings = null;
$this->bShowObsoleteData = $oSet->GetShowObsoleteData();
}
/**
* @param \WebPage $oPage
* @param \DataTableSettings $oSettings
* @param $bActionsMenu
* @param $sSelectMode
* @param $bViewLink
* @param $aExtraParams
*
* @return string
* @throws \CoreException
* @throws \MySQLException
*/
public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
$this->oDefaultSettings = $oSettings;
@@ -119,7 +131,23 @@ class DataTable
return $this->GetAsHTML($oPage, $oCustomSettings->iDefaultPageSize, $oCustomSettings->iDefaultPageSize, 0, $oCustomSettings->aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
/**
* @param \WebPage $oPage
* @param $iPageSize
* @param $iDefaultPageSize
* @param $iPageIndex
* @param $aColumns
* @param $bActionsMenu
* @param $bToolkitMenu
* @param $sSelectMode
* @param $bViewLink
* @param $aExtraParams
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
$sObjectsCount = $this->GetObjectCount($oPage, $sSelectMode);
@@ -198,7 +226,13 @@ class DataTable
}
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $sSelectMode
*
* @return string
*/
protected function GetObjectCount(WebPage $oPage, $sSelectMode)
{
if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
@@ -211,6 +245,15 @@ class DataTable
}
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $iPageSize
* @param $iDefaultPageSize
* @param $iPageIndex
*
* @return string
*/
protected function GetPager(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex)
{
$sHtml = '';
@@ -295,7 +338,17 @@ class DataTable
EOF;
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $aExtraParams
*
* @return string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
*/
protected function GetActionsMenu(WebPage $oPage, $aExtraParams)
{
$oMenuBlock = new MenuBlock($this->oSet->GetFilter(), 'list');
@@ -303,13 +356,20 @@ EOF;
$sHtml = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId);
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $aExtraParams
*
* @return string
* @throws \Exception
*/
protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
{
if (!$oPage->IsPrintableVersion())
{
$sMenuTitle = Dict::S('UI:ConfigureThisList');
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png?t='.utils::GetCacheBusterTimestamp().'"><ul>';
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
$aActions = array(
@@ -326,7 +386,15 @@ EOF;
}
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $aColumns
* @param $bViewLink
* @param $iDefaultPageSize
*
* @return string
*/
protected function GetTableConfigDlg(WebPage $oPage, $aColumns, $bViewLink, $iDefaultPageSize)
{
$sHtml = "<div id=\"datatable_dlg_{$this->iListId}\" style=\"display: none;\">";
@@ -362,23 +430,42 @@ EOF;
return $sHtml;
}
/**
* @param $oSetting
*
* @return array
*/
public function GetAsHash($oSetting)
{
$aSettings = array('iDefaultPageSize' => $oSetting->iDefaultPageSize, 'oColumns' => $oSetting->aColumns);
return $aSettings;
}
/**
* @param array $aColumns
* @param string $sSelectMode
* @param bool $bViewLink
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
*/
protected function GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink)
{
$aAttribs = array();
if ($sSelectMode == 'multiple')
{
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
$aAttribs['form::select'] = array(
'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>",
'description' => Dict::S('UI:SelectAllToggle+'),
'metadata' => array(),
);
}
else if ($sSelectMode == 'single')
{
$aAttribs['form::select'] = array('label' => "", 'description' => '');
$aAttribs['form::select'] = array('label' => '', 'description' => '', 'metadata' => array());
}
foreach($this->aClassAliases as $sAlias => $sClassName)
@@ -389,19 +476,55 @@ EOF;
{
if ($sAttCode == '_key_')
{
$aAttribs['key_'.$sAlias] = array('label' => MetaModel::GetName($sClassName), 'description' => '');
$sAttLabel = MetaModel::GetName($sClassName);
$aAttribs['key_'.$sAlias] = array(
'label' => $sAttLabel,
'description' => '',
'metadata' => array(
'object_class' => $sClassName,
'attribute_label' => $sAttLabel,
),
);
}
else
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
$aAttribs[$sAttCode.'_'.$sAlias] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => $oAttDef->GetOrderByHint());
$sAttDefClass = get_class($oAttDef);
$sAttLabel = MetaModel::GetLabel($sClassName, $sAttCode);
$aAttribs[$sAttCode.'_'.$sAlias] = array(
'label' => $sAttLabel,
'description' => $oAttDef->GetOrderByHint(),
'metadata' => array(
'object_class' => $sClassName,
'attribute_code' => $sAttCode,
'attribute_type' => $sAttDefClass,
'attribute_label' => $sAttLabel,
),
);
}
}
}
}
return $aAttribs;
}
/**
* @param $aColumns
* @param $sSelectMode
* @param $iPageSize
* @param $bViewLink
* @param $aExtraParams
*
* @return array
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
protected function GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$bLocalize = true;
@@ -411,6 +534,7 @@ EOF;
}
$aValues = array();
$aAttDefsCache = array();
$this->oSet->Seek(0);
$iMaxObjects = $iPageSize;
while (($aObjects = $this->oSet->FetchAssoc()) && ($iMaxObjects != 0))
@@ -451,11 +575,41 @@ EOF;
{
if ($sAttCode == '_key_')
{
$aRow['key_'.$sAlias] = $aObjects[$sAlias]->GetHyperLink();
$aRow['key_'.$sAlias] = array(
'value_raw' => $aObjects[$sAlias]->GetKey(),
'value_html' => $aObjects[$sAlias]->GetHyperLink(),
);
}
else
{
$aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode, $bLocalize);
// Prepare att. def. classes cache to avoid retrieving AttDef for each row
if(!isset($aAttDefsCache[$sClassName][$sAttCode]))
{
$aAttDefClassesCache[$sClassName][$sAttCode] = get_class(MetaModel::GetAttributeDef($sClassName, $sAttCode));
}
// Only retrieve raw (stored) value for simple fields
$bExcludeRawValue = false;
foreach (cmdbAbstractObject::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
{
if (is_a($aAttDefClassesCache[$sClassName][$sAttCode], $sAttDefClassToExclude, true))
{
$bExcludeRawValue = true;
break;
}
}
if($bExcludeRawValue)
{
$aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode, $bLocalize);
}
else
{
$aRow[$sAttCode.'_'.$sAlias] = array(
'value_raw' => $aObjects[$sAlias]->Get($sAttCode),
'value_html' => $aObjects[$sAlias]->GetAsHTML($sAttCode, $bLocalize),
);
}
}
}
}
@@ -484,7 +638,25 @@ EOF;
}
return $aValues;
}
/**
* @param \WebPage $oPage
* @param $aColumns
* @param $sSelectMode
* @param $iPageSize
* @param $bViewLink
* @param $aExtraParams
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
@@ -496,7 +668,7 @@ EOF;
$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sHtml = '<table class="listContainer">';
$sHtml = '<table class="listContainer object-list">';
foreach($this->oSet->GetFilter()->GetInternalParams() as $sName => $sValue)
{
@@ -585,7 +757,12 @@ EOF
}
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param $iDefaultPageSize
* @param $iStart
*/
public function UpdatePager(WebPage $oPage, $iDefaultPageSize, $iStart)
{
$iPageSize = $iDefaultPageSize;
@@ -609,11 +786,48 @@ EOF
*/
class PrintableDataTable extends DataTable
{
/**
* @param \WebPage $oPage
* @param $iPageSize
* @param $iDefaultPageSize
* @param $iPageIndex
* @param $aColumns
* @param $bActionsMenu
* @param $bToolkitMenu
* @param $sSelectMode
* @param $bViewLink
* @param $aExtraParams
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
return $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, -1, $bViewLink, $aExtraParams);
}
/**
* @param \WebPage $oPage
* @param $aColumns
* @param $sSelectMode
* @param $iPageSize
* @param $bViewLink
* @param $aExtraParams
*
* @return string
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
@@ -638,7 +852,13 @@ class DataTableSettings implements Serializable
public $iDefaultPageSize;
public $aColumns;
/**
* DataTableSettings constructor.
*
* @param $aClassAliases
* @param null $sTableId
*/
public function __construct($aClassAliases, $sTableId = null)
{
$this->aClassAliases = $aClassAliases;
@@ -646,14 +866,22 @@ class DataTableSettings implements Serializable
$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
@@ -679,7 +907,12 @@ class DataTableSettings implements Serializable
)
);
}
/**
* @param string $sData
*
* @throws \Exception
*/
public function unserialize($sData)
{
$aData = unserialize($sData);
@@ -712,7 +945,16 @@ class DataTableSettings implements Serializable
}
$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);
@@ -762,7 +1004,10 @@ class DataTableSettings implements Serializable
$oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns);
return $oSettings;
}
/**
* @throws \CoreException
*/
protected function FixVisibleColumns()
{
foreach($this->aClassAliases as $sAlias => $sClass)
@@ -799,7 +1044,15 @@ class DataTableSettings implements Serializable
}
}
}
/**
* @param $aClassAliases
* @param null $sTableId
* @param bool $bOnlyOnTable
*
* @return \DataTableSettings|null
* @throws \Exception
*/
static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false)
{
$pref = null;
@@ -828,7 +1081,10 @@ class DataTableSettings implements Serializable
return $oSettings;
}
/**
* @return array
*/
public function GetSortOrder()
{
$aSortOrder = array();
@@ -846,7 +1102,12 @@ class DataTableSettings implements Serializable
}
return $aSortOrder;
}
/**
* @param null $sTargetTableId
*
* @return bool
*/
public function Save($sTargetTableId = null)
{
$sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId;
@@ -857,6 +1118,9 @@ class DataTableSettings implements Serializable
return true;
}
/**
* @return bool
*/
public function SaveAsDefault()
{
$sSettings = $this->serialize();
@@ -886,18 +1150,43 @@ class DataTableSettings implements Serializable
}
return true;
}
/**
* @param null $sTableId
*
* @return string
*/
protected function GetPrefsKey($sTableId = null)
{
if ($sTableId == null) $sTableId = '*';
return static::GetAppUserPreferenceKey($this->aClassAliases, $sTableId);
}
public static function GetAppUserPreferenceKey($aClassAliases, $sTableId)
{
if ($sTableId === null)
{
$sTableId = '*';
}
$aKeys = array();
foreach($this->aClassAliases as $sAlias => $sClass)
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;

View File

@@ -1,26 +1,20 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* DisplayBlock and derived class
* Copyright (C) 2013-2019 Combodo SARL
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
@@ -91,7 +85,7 @@ class DisplayBlock
{
$oDummyFilter = new DBObjectSearch($oSet->GetClass());
$aKeys = array();
$oSet->OptimizeColumnLoad(array('id')); // No need to load all the columns just to get the id
$oSet->OptimizeColumnLoad(array($oSet->GetClassAlias() => array())); // No need to load all the columns just to get the id
while($oObject = $oSet->Fetch())
{
$aKeys[] = $oObject->GetKey();
@@ -1886,11 +1880,11 @@ class MenuBlock extends DisplayBlock
{
if (count($aFavoriteActions) > 0)
{
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
}
else
{
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
}
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);

View File

@@ -395,6 +395,7 @@ EOF
{
foreach($aFields as $oField)
{
/** @var \DesignerFormField $oField */
$oField->ReadParam($aValues);
}
}
@@ -679,18 +680,34 @@ class DesignerTabularForm extends DesignerForm
class DesignerFormField
{
/** @var string $sLabel */
protected $sLabel;
/** @var string $sCode */
protected $sCode;
/** @var mixed $defaultValue */
protected $defaultValue;
/** @var \DesignerForm $oForm */
protected $oForm;
/** @var bool $bMandatory */
protected $bMandatory;
/** @var bool $bReadOnly */
protected $bReadOnly;
/** @var bool $bAutoApply */
protected $bAutoApply;
/** @var array $aCSSClasses */
protected $aCSSClasses;
/** @var bool $bDisplayed */
protected $bDisplayed;
/** @var array $aWidgetExtraParams */
protected $aWidgetExtraParams;
/**
* DesignerFormField constructor.
*
* @param string $sCode
* @param string $sLabel
* @param mixed $defaultValue
*/
public function __construct($sCode, $sLabel, $defaultValue)
{
$this->sLabel = $sLabel;
@@ -703,7 +720,10 @@ class DesignerFormField
$this->bDisplayed = true;
$this->aWidgetExtraParams = array();
}
/**
* @return string
*/
public function GetCode()
{
return $this->sCode;
@@ -712,69 +732,108 @@ class DesignerFormField
/**
* @param \DesignerForm $oForm
*/
public function SetForm(\DesignerForm $oForm)
public function SetForm(DesignerForm $oForm)
{
$this->oForm = $oForm;
}
/**
* @param bool $bMandatory
*/
public function SetMandatory($bMandatory = true)
{
$this->bMandatory = $bMandatory;
}
/**
* @param bool $bReadOnly
*/
public function SetReadOnly($bReadOnly = true)
{
$this->bReadOnly = $bReadOnly;
}
/**
* @return bool
*/
public function IsReadOnly()
{
return ($this->oForm->IsReadOnly() || $this->bReadOnly);
}
/**
* @param bool $bAutoApply
*/
public function SetAutoApply($bAutoApply)
{
$this->bAutoApply = $bAutoApply;
}
/**
* @return bool
*/
public function IsAutoApply()
{
return $this->bAutoApply;
}
/**
* @param bool $bDisplayed
*/
public function SetDisplayed($bDisplayed)
{
$this->bDisplayed = $bDisplayed;
}
/**
* @return bool
*/
public function IsDisplayed()
{
return $this->bDisplayed;
}
/**
* @return string
*/
public function GetFieldId()
{
return $this->oForm->GetFieldId($this->sCode);
}
/**
* @return string
*/
public function GetWidgetClass()
{
return 'property_field';
}
/**
* @return array
*/
public function GetWidgetExtraParams()
{
return $this->aWidgetExtraParams;
}
/**
* @param \WebPage $oP
* @param string $sFormId
* @param string $sRenderMode
*
* @return array
*/
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
}
/**
* @param array $aValues
*/
public function ReadParam(&$aValues)
{
if ($this->IsReadOnly())
@@ -801,12 +860,18 @@ class DesignerFormField
}
}
}
/**
* @return bool
*/
public function IsVisible()
{
return true;
}
/**
* @param string $sCSSClass
*/
public function AddCSSClass($sCSSClass)
{
$this->aCSSClasses[] = $sCSSClass;
@@ -814,6 +879,8 @@ class DesignerFormField
/**
* A way to set/change the default value after constructing the field
*
* @param array $aAllDefaultValue
*/
public function SetDefaultValueFrom($aAllDefaultValue)
{
@@ -822,7 +889,12 @@ class DesignerFormField
$this->defaultValue = $aAllDefaultValue[$this->GetCode()];
}
}
/**
* @param $sFieldCode
*
* @return \DesignerFormField|false
*/
public function FindField($sFieldCode)
{
if ($this->sCode == $sFieldCode)
@@ -832,11 +904,17 @@ class DesignerFormField
return false;
}
/**
* @return string
*/
public function GetHandlerEquals()
{
return 'null';
}
/**
* @return string
*/
public function GetHandlerGetValue()
{
return 'null';
@@ -845,25 +923,43 @@ class DesignerFormField
class DesignerLabelField extends DesignerFormField
{
/** @var int $iCount A counter to automatically make the field code */
protected static $iCount = 0;
/** @var string $sDescription */
protected $sDescription;
/**
* @inheritdoc
*/
public function __construct($sLabel, $sDescription)
{
parent::__construct('', $sLabel, '');
// Increase counter
static::$iCount++;
parent::__construct('label_number_' . static::$iCount, $sLabel, '');
$this->sDescription = $sDescription;
}
/**
* @inheritdoc
*/
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
return array('label' => $this->sLabel, 'value' => $this->sDescription);
}
/**
* @inheritdoc
*/
public function ReadParam(&$aValues)
{
}
/**
* @inheritdoc
*/
public function IsVisible()
{
return true;
@@ -1334,7 +1430,8 @@ class DesignerIconSelectionField extends DesignerFormField
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
if (!$this->IsReadOnly())
{
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$this->defaultValue}\"/>";
$sDefaultValue = ($this->defaultValue !== '') ? $this->defaultValue : $this->aAllowedValues[$idx]['value'];
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/>";
$oP->add_ready_script(
<<<EOF
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
@@ -1396,6 +1493,7 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
$sAvailableIcons .= ' static $sKey = '.var_export($sKey, true).';'.PHP_EOL;
$sAvailableIcons .= ' static $aIconFiles = '.var_export($aFiles, true).';'.PHP_EOL;
$sAvailableIcons .= '}'.PHP_EOL;
SetupUtils::builddir(dirname($sCacheFile));
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
}
return $aFiles;

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class iTopWebPage
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT."/application/nicewebpage.class.inc.php");
@@ -46,11 +39,19 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
protected $sBreadCrumbEntryIcon;
protected $oCtx;
/**
* iTopWebPage constructor.
*
* @param string $sTitle
* @param bool $bPrintable
*
* @throws \Exception
*/
public function __construct($sTitle, $bPrintable = false)
{
parent::__construct($sTitle, $bPrintable);
$this->m_oTabs = new TabManager();
$this->oCtx = new ContextTag('GUI:Console');
$this->oCtx = new ContextTag(ContextTag::TAG_CONSOLE);
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
@@ -79,6 +80,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_stylesheet("../css/c3.min.css");
$this->add_linked_stylesheet("../css/font-awesome/css/all.min.css");
$this->add_linked_stylesheet("../css/font-awesome/css/v4-shims.min.css");
$this->add_linked_stylesheet("../js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css");
$this->add_linked_script('../js/jquery.layout.min.js');
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
@@ -92,6 +94,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_script("../js/swfobject.js");
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$this->add_linked_script("../js/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js");
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script('../js/property_field.js');
$this->add_linked_script('../js/icon_select.js');
@@ -150,6 +153,9 @@ EOF
}
}
/**
* @return bool
*/
protected function IsMenuPaneVisible()
{
$bLeftPaneOpen = true;
@@ -172,6 +178,9 @@ EOF
return $bLeftPaneOpen;
}
/**
*
*/
protected function PrepareLayout()
{
if (MetaModel::GetConfig()->Get('demo_mode'))
@@ -343,6 +352,20 @@ JS
.magnificPopup({type: 'image', closeOnContentClick: true });
JS
);
// Highlight code content created with CKEditor
$this->add_ready_script(
<<<JS
// Highlight code content for HTML AttributeText
$("[data-attribute-type='AttributeText'] .HTML pre").each(function(i, block) {
hljs.highlightBlock(block);
});
// Highlight code content for CaseLogs
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre").each(function(i, block) {
hljs.highlightBlock(block);
});
JS
);
$this->add_init_script(
<<< JS
@@ -736,11 +759,22 @@ JS
$this->sBreadCrumbEntryIcon = null;
}
/**
* @param string $sHtml
*/
public function AddToMenu($sHtml)
{
$this->m_sMenu .= $sHtml;
}
/**
* @return string
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function GetSiloSelectionForm()
{
// List of visible Organizations
@@ -801,6 +835,9 @@ JS
return $sHtml;
}
/**
* @throws \DictExceptionMissingString
*/
public function DisplayMenu()
{
// Display the menu
@@ -816,6 +853,8 @@ JS
protected function InitNewsroom()
{
$sNewsroomInitialImage = '';
$aProviderParams = array();
if (MetaModel::GetConfig()->Get('newsroom_enabled') !== false)
{
$oUser = UserRights::GetUserObject();
@@ -823,30 +862,31 @@ JS
* @var iNewsroomProvider[] $aProviders
*/
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
$aProviderParams = array();
foreach($aProviders as $oProvider)
{
$oProvider->SetConfig(MetaModel::GetConfig());
$bProviderEnabled = appUserPreferences::GetPref('newsroom_provider_'.get_class($oProvider), true);
if ($bProviderEnabled && $oProvider->IsApplicable($oUser))
{
$aProviderParams[] = array(
'label' => $oProvider->GetLabel(),
'fetch_url' => $oProvider->GetFetchURL(),
'view_all_url' => $oProvider->GetViewAllURL(),
'mark_all_as_read_url' => $oProvider->GetMarkAllAsReadURL(),
'placeholders' => $oProvider->GetPlaceholders(),
'ttl' => $oProvider->GetTTL(),
);
$bProviderEnabled = appUserPreferences::GetPref('newsroom_provider_'.get_class($oProvider),true);
if ($bProviderEnabled && $oProvider->IsApplicable($oUser))
{
$aProviderParams[] = array(
'label' => $oProvider->GetLabel(),
'fetch_url' => $oProvider->GetFetchURL(),
'view_all_url' => $oProvider->GetViewAllURL(),
'mark_all_as_read_url' => $oProvider->GetMarkAllAsReadURL(),
'placeholders' => $oProvider->GetPlaceholders(),
'ttl' => $oProvider->GetTTL(),
);
}
}
}
// Show newsroom only if there are some providers
if (count($aProviderParams) > 0)
{
$sImageUrl= '../images/newsroom_menu.png';
$sPlaceholderImageUrl= '../images/newsroom-message.svg';
$sImageUrl= 'fas fa-comment-dots';
$sPlaceholderImageUrl= 'far fa-envelope';
$aParams = array(
'image_url' => $sImageUrl,
'placeholder_image_url' => $sPlaceholderImageUrl,
'image_icon' => $sImageUrl,
'placeholder_image_icon' => $sPlaceholderImageUrl,
'cache_uuid' => 'itop-newsroom-'.UserRights::GetUserId().'-'.md5(APPROOT),
'providers' => $aProviderParams,
'display_limit' => (int)appUserPreferences::GetPref('newsroom_display_size', 7),
@@ -862,20 +902,16 @@ JS
$('#top-left-newsroom-cell').newsroom_menu($sParams);
EOF
);
$sNewsroomInitialImage = '<img style="opacity:0.4" src="../images/newsroom_menu.png">';
$sNewsroomInitialImage = '<i style="opacity:0.4" class="top-right-icon fas fa-comment-dots"></i>';
}
else
{
// No newsroom menu at all
}
}
// else no newsroom menu
return $sNewsroomInitialImage;
// else no newsroom menu
return $sNewsroomInitialImage;
}
/**
* Outputs (via some echo) the complete HTML page by assembling all its elements
* @inheritDoc
* @throws \Exception
*/
public function output()
{
@@ -1109,7 +1145,7 @@ EOF
{
$sBodyClass = 'printable-version';
}
$sHtml .= "<body class=\"$sBodyClass\">\n";
$sHtml .= "<body class=\"$sBodyClass\" data-gui-type=\"backoffice\">\n";
if ($this->IsPrintableVersion())
{
$sHtml .= "<div class=\"explain-printable not-printable\">";
@@ -1146,7 +1182,7 @@ EOF;
}
// Render the revision number
if (ITOP_REVISION == '$WCREV$')
if (ITOP_REVISION == 'svn')
{
// This is NOT a version built using the buil system, just display the main version
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
@@ -1182,7 +1218,7 @@ EOF;
{
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
}
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><img src=\"../images/on-off-menu.png\"><ul>";
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
$aActions = array();
@@ -1344,10 +1380,10 @@ EOF;
$sHtml .= ' <table id="top-bar-table">';
$sHtml .= ' <tr>';
$sHtml .= ' <td id="open-left-pane" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'" onclick="$(\'body\').layout().open(\'west\');">';
$sHtml .= ' <img src="../images/menu.png">';
$sHtml .= ' <i class="fas fa-bars"></i>';
$sHtml .= ' </td>';
$sHtml .= ' <td id="go-home" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
$sHtml .= ' <a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><img src="../images/home.png"></a>';
$sHtml .= ' <a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><i class="fas fa-home"></i></a>';
$sHtml .= ' </td>';
$sHtml .= ' <td class="top-bar-spacer menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
$sHtml .= ' </td>';
@@ -1357,8 +1393,8 @@ EOF;
$sHtml .= ' <td id="top-bar-table-search">';
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">';
$sHtml .= ' <table id="top-left-buttons-area"><tr>';
$sHtml .= ' <td id="top-left-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sDefaultPlaceHolder.'" value="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"><input type="hidden" name="operation" value="full_text"/></div></div></td>';
$sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?t='.utils::GetCacheBusterTimestamp().'"/></td>';
$sHtml .= ' <td id="top-left-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sDefaultPlaceHolder.'" value="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"><i class="top-right-icon fa-flip-horizontal fas fa-search"></i><input type="hidden" name="operation" value="full_text"/></div></div></td>';
$sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank" title="'.Dict::S('UI:Help').'"><i class="top-right-icon fas fa-question-circle"></i></a></td>';
$sHtml .= ' <td id="top-left-newsroom-cell">'.$sNewsRoomInitialImage.'</td>';
$sHtml .= ' <td id="top-left-logoff-cell">'.self::FilterXSS($sLogOffMenu).'</td>';
$sHtml .= ' </tr></table></form></div>';
@@ -1413,9 +1449,12 @@ EOF;
{
if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf'))
{
// Note: Apparently this was a demand from ITOMIG a while back, so it's not "dead code" per say.
// The last trace we got is in R-007989. Do not remove this without checking before with the concerned parties if it is still used!
if (@is_readable(APPROOT.'lib/MPDF/mpdf.php'))
{
require_once(APPROOT.'lib/MPDF/mpdf.php');
/** @noinspection PhpUndefinedClassInspection Check above comment */
$oMPDF = new mPDF('c');
$oMPDF->mirroMargins = false;
if ($this->a_base['href'] != '')
@@ -1442,59 +1481,68 @@ EOF;
ExecutionKPI::ReportStats();
}
/**
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
/**
* @inheritDoc
* @throws \Exception
*/
public function AddToTab($sTabContainer, $sTabCode, $sHtml)
{
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabLabel, $sHtml));
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabCode, $sHtml));
}
/**
* @inheritDoc
*/
public function SetCurrentTabContainer($sTabContainer = '')
{
return $this->m_oTabs->SetCurrentTabContainer($sTabContainer);
}
public function SetCurrentTab($sTabLabel = '')
/**
* @inheritDoc
*/
public function SetCurrentTab($sTabCode = '', $sTabTitle = null)
{
return $this->m_oTabs->SetCurrentTab($sTabLabel);
return $this->m_oTabs->SetCurrentTab($sTabCode, $sTabTitle);
}
/**
* Add a tab which content will be loaded asynchronously via the supplied URL
*
* Limitations:
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from
* another server. Static content cannot be added inside such tabs.
*
* @param string $sTabLabel The (localised) label of the tab
* @param string $sUrl The URL to load (on the same server)
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be
* reloaded upon each activation.
*
* @inheritDoc
* @throws \Exception
* @since 2.0.3
*/
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
public function AddAjaxTab($sTabCode, $sUrl, $bCache = true, $sTabTitle = null)
{
$this->add($this->m_oTabs->AddAjaxTab($sTabLabel, $sUrl, $bCache));
$this->add($this->m_oTabs->AddAjaxTab($sTabCode, $sUrl, $bCache, $sTabTitle));
}
/**
* @inheritDoc
*/
public function GetCurrentTab()
{
return $this->m_oTabs->GetCurrentTab();
}
public function RemoveTab($sTabLabel, $sTabContainer = null)
/**
* @inheritDoc
*/
public function RemoveTab($sTabCode, $sTabContainer = null)
{
$this->m_oTabs->RemoveTab($sTabLabel, $sTabContainer);
$this->m_oTabs->RemoveTab($sTabCode, $sTabContainer);
}
/**
* Finds the tab whose title matches a given pattern
*
* @return mixed The name of the tab as a string or false if not found
* @inheritDoc
*/
public function FindTab($sPattern, $sTabContainer = null)
{
@@ -1506,12 +1554,19 @@ EOF;
* 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...
*
* @param string $sTabContainer
* @param string $sTabCode
*/
public function SelectTab($sTabContainer, $sTabLabel)
public function SelectTab($sTabContainer, $sTabCode)
{
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabLabel));
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabCode));
}
/**
* @inheritDoc
* @throws \Exception
*/
public function add($sHtml)
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
@@ -1525,9 +1580,7 @@ EOF;
}
/**
* Records the current state of the 'html' part of the page output
*
* @return mixed The current state of the 'html' output
* @inheritDoc
*/
public function start_capture()
{
@@ -1547,12 +1600,7 @@ EOF;
}
/**
* Returns the part of the html output that occurred since the call to start_capture
* and removes this part from the current html output
*
* @param $offset mixed The value returned by start_capture
*
* @return string The part of the html output that was added since the call to start_capture
* @inheritDoc
*/
public function end_capture($offset)
{
@@ -1577,6 +1625,8 @@ EOF;
/**
* Set the message to be displayed in the 'app-banner' section at the top of the page
*
* @param string $sHtmlMessage
*/
public function SetMessage($sHtmlMessage)
{
@@ -1586,6 +1636,10 @@ EOF;
/**
* Add message to be displayed in the 'app-banner' section at the top of the page
*
* @param string $sHtmlMessage
* @param string|null $sHtmlIcon
* @param string|null $sTip
*/
public function AddApplicationMessage($sHtmlMessage, $sHtmlIcon = null, $sTip = null)
{
@@ -1606,7 +1660,8 @@ EOF;
* @param string $sContent
* @param string $sCssClasses CSS classes to add to the container
*
* @since 2.6
* @throws \Exception
* @since 2.6.0
*/
public function AddHeaderMessage($sContent, $sCssClasses = 'message_info')
{
@@ -1619,6 +1674,8 @@ EOF
/**
* Adds a script to be executed when the DOM is ready (typical JQuery use), right before add_ready_script
*
* @param string $sScript
*
* @return void
*/
public function add_init_script($sScript)

View File

@@ -1,13 +1,16 @@
<?php
/**
* Class LoginForm
*
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
/**
* Class LoginForm
*
* @since 2.7.0
*/
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
{
private $bForceFormOnError = false;
@@ -21,6 +24,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
return array('form');
}
/**
* @inheritDoc
*/
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'form'))
@@ -51,6 +57,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -66,6 +75,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -85,6 +97,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -94,6 +109,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -105,14 +123,14 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
}
/**
* @return LoginTwigData
* @inheritDoc
* @throws \Exception
*/
public function GetLoginData()
public function GetTwigContext()
{
$aPostedVars = array('auth_user', 'auth_pwd');
$oLoginData = new LoginTwigData($aPostedVars);
$oLoginContext = new LoginTwigContext();
$oLoginContext->AddPostedVar('auth_user');
$oLoginContext->AddPostedVar('auth_pwd');
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
@@ -121,19 +139,18 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
'sAuthUser' => $sAuthUser,
'sAuthPwd' => $sAuthPwd,
);
$oLoginData->AddBlockData('login_input', new LoginBlockData('loginforminput.html.twig', $aData));
$oLoginData->AddBlockData('login_submit', new LoginBlockData('loginformsubmit.html.twig'));
$oLoginData->AddBlockData('login_form_footer', new LoginBlockData('loginformfooter.html.twig'));
$oLoginContext->AddBlockExtension('login_input', new LoginBlockExtension('extensionblock/loginforminput.html.twig', $aData));
$oLoginContext->AddBlockExtension('login_submit', new LoginBlockExtension('extensionblock/loginformsubmit.html.twig'));
$oLoginContext->AddBlockExtension('login_form_footer', new LoginBlockExtension('extensionblock/loginformfooter.html.twig'));
$bEnableResetPassword = empty(MetaModel::GetConfig()->Get('forgot_password')) ? true : MetaModel::GetConfig()->Get('forgot_password');
$bEnableResetPassword = MetaModel::GetConfig()->Get('forgot_password');
$sResetPasswordUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
$aData = array(
'bEnableResetPassword' => $bEnableResetPassword,
'sResetPasswordUrl' => $sResetPasswordUrl,
);
$oLoginData->AddBlockData('login_links', new LoginBlockData('loginformlinks.html.twig', $aData));
$oLoginContext->AddBlockExtension('login_links', new LoginBlockExtension('extensionblock/loginformlinks.html.twig', $aData));
return $oLoginData;
return $oLoginContext;
}
}

View File

@@ -9,61 +9,130 @@
use Combodo\iTop\TwigExtension;
class LoginTwigData
/**
* Twig context for modules extending the login screen
* Class LoginTwigContext
*/
class LoginTwigContext
{
private $aBlockData;
/** @var array */
private $aBlockExtension;
/** @var array */
private $aPostedVars;
/** @var string */
private $sTwigLoaderPath;
private $sCSSFile;
/** @var array */
private $aCSSFiles;
/** @var array */
private $aJsFiles;
private $sTwigNameSpace;
/**
* LoginTwigData constructor.
* Build a context to display the twig files used
* to extend the login screens
*
* @param array $aPostedVars
* @param string $sLoaderPath
* @param string $sCSSFile
* @param array $aJsFiles
* LoginTwigContext constructor.
* @api
*/
public function __construct($aPostedVars = array(), $sLoaderPath = null, $sCSSFile = null, $aJsFiles = array())
public function __construct()
{
$this->aBlockData = array();
$this->aPostedVars = $aPostedVars;
$this->sTwigLoaderPath = $sLoaderPath;
$this->sCSSFile = $sCSSFile;
$this->aJsFiles = $aJsFiles;
$this->aBlockExtension = array();
$this->aPostedVars = array();
$this->sTwigLoaderPath = null;
$this->aCSSFiles = array();
$this->aJsFiles = array();
$this->sTwigNameSpace = null;
}
/**
* Set the absolute path on disk of the folder containing the twig templates
*
* @param string $sPath absolute path of twig templates directory
* @api
*/
public function SetLoaderPath($sPath)
{
$this->sTwigLoaderPath = $sPath;
}
/**
* Add a Twig block extension
*
* @param string $sBlockName
* @param LoginBlockExtension $oBlockExtension
*/
public function AddBlockExtension($sBlockName, $oBlockExtension)
{
$this->aBlockExtension[$sBlockName] = $oBlockExtension;
}
/**
* Add a variable intended to be posted on URL (and managed) by the module.
* Declaring the posted variables will prevent the core engine to manipulate these variables.
*
* @param string $sPostedVar Name of the posted variable
* @api
*/
public function AddPostedVar($sPostedVar)
{
$this->aPostedVars[] = $sPostedVar;
}
/**
* Add the URL of a CSS file to link to the login screen
*
* @param string $sFile URL of the CSS file to link
* @api
*/
public function AddCSSFile($sFile)
{
$this->aCSSFiles[] = $sFile;
}
/**
* Add the URL of a javascript file to link to the login screen
* @param string $sFile URL of the javascript file to link
* @api
*/
public function AddJsFile($sFile)
{
$this->aJsFiles[] = $sFile;
}
/**
* @param string $sBlockName
* @param LoginBlockData $oBlockData
*
* @return \LoginBlockExtension
*/
public final function AddBlockData($sBlockName, $oBlockData)
public function GetBlockExtension($sBlockName)
{
$this->aBlockData[$sBlockName] = $oBlockData;
/** @var LoginBlockExtension $oBlockExtension */
$oBlockExtension = isset($this->aBlockExtension[$sBlockName]) ? $this->aBlockExtension[$sBlockName] : null;
return $oBlockExtension;
}
public final function GetBlockData($sBlockName)
{
/** @var LoginBlockData $oBlockData */
$oBlockData = isset($this->aBlockData[$sBlockName]) ? $this->aBlockData[$sBlockName] : null;
return $oBlockData;
}
public final function GetPostedVars()
/**
* @return array
*/
public function GetPostedVars()
{
return $this->aPostedVars;
}
public final function GetTwigLoaderPath()
/**
* @return string
*/
public function GetTwigLoaderPath()
{
return $this->sTwigLoaderPath;
}
public final function GetCSSFile()
/**
* @return array
*/
public function GetCSSFiles()
{
return $this->sCSSFile;
return $this->aCSSFiles;
}
/**
@@ -73,18 +142,33 @@ class LoginTwigData
{
return $this->aJsFiles;
}
}
class LoginBlockData
/**
* Twig block description for login screen extension
* The login screen can be extended by adding twig templates
* to specific blocks of the login screens
*
* Class LoginBlockExtension
*/
class LoginBlockExtension
{
private $sTwig;
private $aData;
/**
* LoginBlockData constructor.
* Create a new twig extension block
* The given twig template can be HTML, CSS or JavaScript.
* CSS goes to the block named 'css' and is inline in the page.
* JavaScript goes to the blocks named 'script' or 'ready_script' and are inline in the page.
* HTML goes to everywhere else
*
* @param string $sTwig
* @param array $aData
* LoginBlockExtension constructor.
*
* @param string $sTwig name of the twig file relative to the path given to the LoginTwigContext
* @param array $aData Data given to the twig template (into the variable {{ aData }})
* @api
*/
public function __construct($sTwig, $aData = array())
{
@@ -92,18 +176,22 @@ class LoginBlockData
$this->aData = $aData;
}
public final function GetTwig()
public function GetTwig()
{
return $this->sTwig;
}
public final function GetData()
public function GetData()
{
return $this->aData;
}
}
class LoginTwigContext
/**
* Used by LoginWebPage to display the login screen
* Class LoginTwigRenderer
*/
class LoginTwigRenderer
{
private $aLoginPluginList;
private $aPluginFormData;
@@ -112,21 +200,27 @@ class LoginTwigContext
public function __construct()
{
$this->aLoginPluginList = LoginWebPage::GetLoginPluginList('iLoginDataExtension', false);
$this->aLoginPluginList = LoginWebPage::GetLoginPluginList('iLoginUIExtension', false);
$this->aPluginFormData = array();
$aTwigLoaders = array();
$this->aPostedVars = array();
foreach ($this->aLoginPluginList as $oLoginPlugin)
{
/** @var \iLoginDataExtension $oLoginPlugin */
$oLoginData = $oLoginPlugin->GetLoginData();
$this->aPluginFormData[] = $oLoginData;
$sTwigLoaderPath = $oLoginData->GetTwigLoaderPath();
/** @var \iLoginUIExtension $oLoginPlugin */
$oLoginContext = $oLoginPlugin->GetTwigContext();
if (is_null($oLoginContext))
{
continue;
}
$this->aPluginFormData[] = $oLoginContext;
$sTwigLoaderPath = $oLoginContext->GetTwigLoaderPath();
if ($sTwigLoaderPath != null)
{
$aTwigLoaders[] = new Twig_Loader_Filesystem($sTwigLoaderPath);
$oExtensionLoader = new Twig_Loader_Filesystem();
$oExtensionLoader->setPaths($sTwigLoaderPath);
$aTwigLoaders[] = $oExtensionLoader;
}
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginData->GetPostedVars());
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginContext->GetPostedVars());
}
$oCoreLoader = new Twig_Loader_Filesystem(array(), APPROOT.'templates');
@@ -178,9 +272,9 @@ class LoginTwigContext
// Render CSS links
foreach ($this->aPluginFormData as $oFormData)
{
/** @var \LoginTwigData $oFormData */
$sCSSFile = $oFormData->GetCSSFile();
if (!empty($sCSSFile))
/** @var \LoginTwigContext $oFormData */
$aCSSFiles = $oFormData->GetCSSFiles();
foreach ($aCSSFiles as $sCSSFile)
{
$oPage->add_linked_stylesheet($sCSSFile);
}
@@ -224,4 +318,4 @@ class LoginTwigContext
{
return $this->oTwig;
}
}
}

View File

@@ -118,7 +118,7 @@ class LoginWebPage extends NiceWebPage
public function DisplayLoginForm($bFailedLogin = false)
{
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aPostedVars = array_merge(array('login_mode', 'loginop'), $oTwigContext->GetPostedVars());
$sMessage = Dict::S('UI:Login:IdentifyYourself');
@@ -171,7 +171,7 @@ class LoginWebPage extends NiceWebPage
{
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
$aVars['bFailedToReset'] = $bFailedToReset;
@@ -189,53 +189,54 @@ class LoginWebPage extends NiceWebPage
UserRights::Login($sAuthUser); // Set the user's language (if possible!)
/** @var UserInternal $oUser */
$oUser = UserRights::GetUserObject();
if ($oUser == null)
if ($oUser != null)
{
throw new Exception(Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser));
}
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token'))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
}
if (!$oUser->CanChangePassword())
{
throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd'));
}
$sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed
if ($sTo == '')
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail'));
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token'))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
}
if (!$oUser->CanChangePassword())
{
throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd'));
}
$sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed
if ($sTo == '')
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail'));
}
// This token allows the user to change the password without knowing the previous one
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
$oUser->Set('reset_pwd_token', $sToken);
CMDBObject::SetTrackInfo('Reset password');
$oUser->AllowWrite(true);
$oUser->DBUpdate();
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject', $oUser->Get('login')));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
$oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl, $oUser->Get('login')));
$iRes = $oEmail->Send($aIssues, true /* force synchronous exec */);
switch ($iRes)
{
//case EMAIL_SEND_PENDING:
case EMAIL_SEND_OK:
break;
case EMAIL_SEND_ERROR:
default:
IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
}
}
// This token allows the user to change the password without knowing the previous one
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
$oUser->Set('reset_pwd_token', $sToken);
CMDBObject::SetTrackInfo('Reset password');
$oUser->AllowWrite(true);
$oUser->DBUpdate();
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject', $oUser->Get('login')));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
$oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl, $oUser->Get('login')));
$iRes = $oEmail->Send($aIssues, true /* force synchronous exec */);
switch ($iRes)
{
//case EMAIL_SEND_PENDING:
case EMAIL_SEND_OK:
break;
case EMAIL_SEND_ERROR:
default:
IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
}
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
}
@@ -245,7 +246,7 @@ class LoginWebPage extends NiceWebPage
}
}
public function DisplayResetPwdForm()
public function DisplayResetPwdForm($sErrorMessage = null)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sToken = utils::ReadParam('token', '', false, 'raw_data');
@@ -253,11 +254,13 @@ class LoginWebPage extends NiceWebPage
UserRights::Login($sAuthUser); // Set the user's language
$oUser = UserRights::GetUserObject();
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
$aVars['sToken'] = $sToken;
$aVars['sErrorMessage'] = $sErrorMessage;
if (($oUser == null))
{
$aVars['bNoUser'] = true;
@@ -265,6 +268,7 @@ class LoginWebPage extends NiceWebPage
else
{
$aVars['bNoUser'] = false;
$aVars['sUserName'] = $oUser->GetFriendlyName();
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
@@ -274,7 +278,6 @@ class LoginWebPage extends NiceWebPage
else
{
$aVars['bBadToken'] = false;
$aVars['sUserName'] = $oUser->GetFriendlyName();
}
}
@@ -288,9 +291,10 @@ class LoginWebPage extends NiceWebPage
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
UserRights::Login($sAuthUser); // Set the user's language
/** @var \UserLocal $oUser */
$oUser = UserRights::GetUserObject();
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
@@ -312,7 +316,7 @@ class LoginWebPage extends NiceWebPage
{
$aVars['bBadToken'] = false;
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->Set('reset_pwd_token', new ormPassword());
$oUser->AllowWrite(true);
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$aVars['sUrl'] = utils::GetAbsoluteUrlAppRoot();
@@ -322,11 +326,12 @@ class LoginWebPage extends NiceWebPage
$oTwigContext->Render($this, 'resetpwddone.html.twig', $aVars);
}
public function DisplayChangePwdForm($bFailedLogin = false)
public function DisplayChangePwdForm($bFailedLogin = false, $sIssue = null)
{
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['bFailedLogin'] = $bFailedLogin;
$aVars['sIssue'] = $sIssue;
$oTwigContext->Render($this, 'changepwdform.html.twig', $aVars);
}
@@ -337,7 +342,7 @@ class LoginWebPage extends NiceWebPage
$sTitle = empty($sTitle) ? Dict::S('UI:LogOff:ThankYou') : $sTitle;
$sMessage = Dict::S('UI:LogOff:ClickHereToLoginAgain');
$oTwigContext = new LoginTwigContext();
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sUrl'] = $sUrl;
$aVars['sTitle'] = $sTitle;
@@ -410,6 +415,7 @@ class LoginWebPage extends NiceWebPage
if ($bLoginDebug)
{
IssueLog::Info("---------------------------------");
IssueLog::Info($_SERVER['REQUEST_URI']);
IssueLog::Info("--> Entering Login FSM with state: [$sLoginState]");
$sSessionLog = session_id().' '.utils::GetSessionLog();
IssueLog::Info("SESSION: $sSessionLog");
@@ -494,6 +500,7 @@ class LoginWebPage extends NiceWebPage
foreach (MetaModel::EnumPlugins($sInterface) as $oLoginExtensionInstance)
{
$aLoginModes = $oLoginExtensionInstance->ListSupportedLoginModes();
$aLoginModes = (is_array($aLoginModes) ? $aLoginModes : array());
foreach ($aLoginModes as $sLoginMode)
{
// Keep only the plugins for the current login mode + before + after
@@ -783,6 +790,13 @@ class LoginWebPage extends NiceWebPage
$oPerson = null;
try
{
$sOrigin = 'External User provisioning';
if (isset($_SESSION['login_mode']))
{
$sOrigin .= " ({$_SESSION['login_mode']})";
}
CMDBObject::SetTrackOrigin($sOrigin);
$oPerson = MetaModel::NewObject('Person');
$oPerson->Set('first_name', $sFirstName);
$oPerson->Set('name', $sLastName);
@@ -797,17 +811,7 @@ class LoginWebPage extends NiceWebPage
{
$oPerson->Set($sAttCode, $sValue);
}
/** @var CMDBChange $oMyChange */
$oMyChange = MetaModel::NewObject('CMDBChange');
$oMyChange->Set("date", time());
$sOrigin = 'External User provisioning';
if (isset($_SESSION['login_mode']))
{
$sOrigin .= " ({$_SESSION['login_mode']})";
}
$oMyChange->Set('userinfo', $sOrigin);
$oMyChange->DBInsert();
$oPerson->DBInsertTracked($oMyChange);
$oPerson->DBInsert();
}
catch (Exception $e)
{
@@ -889,19 +893,7 @@ class LoginWebPage extends NiceWebPage
$oUser->Set('profile_list', $oProfilesSet);
if ($oUser->IsModified())
{
/** @var \CMDBChange $oMyChange */
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set('userinfo', $sOrigin);
$oMyChange->DBInsert();
if ($oUser->IsNew())
{
$oUser->DBInsertTracked($oMyChange);
}
else
{
$oUser->DBUpdateTracked($oMyChange);
}
$oUser->DBWrite();
}
}
catch (Exception $e)
@@ -938,6 +930,7 @@ class LoginWebPage extends NiceWebPage
{
// No rights to be here, redirect to the portal
header('Location: '.$ret);
die();
}
}
return self::EXIT_CODE_OK;
@@ -1044,8 +1037,17 @@ class LoginWebPage extends NiceWebPage
}
else if ($operation == 'do_reset_pwd')
{
$oPage = self::NewLoginWebPage();
$oPage->DoResetPassword();
try {
$oPage = self::NewLoginWebPage();
$oPage->DoResetPassword();
}
catch (CoreCannotSaveObjectException $e)
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayResetPwdForm($e->getIssue());
}
$oPage->output();
exit;
}
@@ -1061,6 +1063,24 @@ class LoginWebPage extends NiceWebPage
exit;
}
}
else if ($operation == 'check_pwd_policy')
{
$sAuthUser = $_SESSION['auth_user'];
UserRights::Login($sAuthUser); // Set the user's language
$aPwdMap = array();
foreach (array('new_pwd', 'retype_new_pwd') as $postedPwd)
{
$oUser = new UserLocal();
$oUser->ValidatePassword($_POST[$postedPwd]);
$aPwdMap[$postedPwd]['isValid'] = $oUser->IsPasswordValid();
$aPwdMap[$postedPwd]['message'] = $oUser->getPasswordValidityMessage();
}
echo json_encode($aPwdMap);
die();
}
if ($operation == 'do_change_pwd')
{
if (isset($_SESSION['auth_user']))
@@ -1069,10 +1089,21 @@ class LoginWebPage extends NiceWebPage
UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
try
{
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm(true); // old pwd was wrong
$oPage->output();
exit;
}
}
catch (CoreCannotSaveObjectException $e)
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm(true); // old pwd was wrong
$oPage->DisplayChangePwdForm(true, $e->getIssue()); // password policy was not met.
$oPage->output();
exit;
}

View File

@@ -1,7 +1,20 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
@@ -18,12 +31,18 @@
function _MaintenanceSetupPageMessage($sTitle, $sMessage)
{
// Web Page
@include_once(APPROOT.'bootstrap.inc.php');
@include_once(APPROOT.'setup/setuppage.class.inc.php');
if (class_exists('SetupPage'))
{
$oP = new SetupPage($sTitle);
$oP->p("<h2>$sMessage</h2>");
$oP->p("<h2 class=\"center\">$sMessage</h2>");
$oP->add_ready_script(
<<<JS
// Reload in 30s to check if maintenance is over
setTimeout(function(){ window.location.reload(); }, 30000);
JS
);
$oP->output();
}
else
@@ -58,7 +77,6 @@ function _MaintenanceHtmlMessage($sMessage)
*/
function _MaintenanceJsonMessage($sTitle, $sMessage)
{
@include_once(APPROOT.'bootstrap.inc.php');
@include_once(APPROOT."/application/ajaxwebpage.class.inc.php");
if (class_exists('ajax_page'))
{

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Construction and display of the application's main menu
* Copyright (C) 2013-2019 Combodo SARL
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT.'/application/utils.inc.php');
@@ -201,7 +194,7 @@ class ApplicationMenu
/**
* Entry point to display the whole menu into the web page, used by iTopWebPage
* @param \iTopWebPage $oPage
* @param \WebPage $oPage
* @param $aExtraParams
* @throws DictExceptionMissingString
*/
@@ -217,7 +210,7 @@ class ApplicationMenu
{
if (!self::CanDisplayMenu($aMenu)) { continue; }
$oMenuNode = self::GetMenuNode($aMenu['index']);
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'">'.$oMenuNode->GetTitle().'</h3>');
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'" class="navigation-menu-group" data-menu-id="'.$oMenuNode->GetMenuId().'">'.$oMenuNode->GetTitle().'</h3>');
$oPage->AddToMenu('<div>');
$oPage->AddToMenu('<ul>');
$aChildren = self::GetChildren($aMenu['index']);
@@ -270,12 +263,15 @@ EOF
/**
* Handles the display of the sub-menus (called recursively if necessary)
* @param \iTopWebPage $oPage
*
* @param \WebPage $oPage
* @param array $aMenus
* @param array $aExtraParams
* @param int $iActiveMenu
*
* @return true if the currently selected menu is one of the submenus
* @throws DictExceptionMissingString
* @throws \Exception
*/
static protected function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
{
@@ -284,14 +280,22 @@ EOF
usort($aMenus, array('ApplicationMenu', 'CompareOnRank'));
foreach($aMenus as $aMenu)
{
if (!self::CanDisplayMenu($aMenu))
{
continue;
}
$index = $aMenu['index'];
$oMenu = self::GetMenuNode($index);
if ($oMenu->IsEnabled())
{
$aChildren = self::GetChildren($index);
$sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : '';
$aCSSClasses = array('navigation-menu-item');
if (count($aChildren) > 0)
{
$aCSSClasses[] = 'submenu';
}
$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
$sItemHtml = '<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" '.$sCSSClass.'>';
$sItemHtml = '<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'" class="'.implode(' ', $aCSSClasses).'" data-menu-id="'.$oMenu->GetMenuID().'">';
if ($sHyperlink != '')
{
$sLinkTarget = '';
@@ -299,7 +303,9 @@ EOF
{
$sLinkTarget .= ' target="_blank"';
}
$sItemHtml .= '<a href="'.$oMenu->GetHyperlink($aExtraParams).'"'.$sLinkTarget.'>'.$oMenu->GetTitle().'</a>';
$sURL = '"'.$oMenu->GetHyperlink($aExtraParams).'"'.$sLinkTarget;
$sTitle = utils::HtmlEntities($oMenu->GetTitle());
$sItemHtml .= "<a href={$sURL}>{$sTitle}</a>";
}
else
{
@@ -607,7 +613,9 @@ abstract class MenuNode
/**
* @param $aExtraParams
*
* @return string
* @throws \Exception
*/
public function GetHyperlink($aExtraParams)
{
@@ -687,8 +695,8 @@ abstract class MenuNode
public abstract function RenderContent(WebPage $oPage, $aExtraParams = array());
/**
* @param $sHyperlink
* @param $aExtraParams
* @param string $sHyperlink
* @param array $aExtraParams
* @return string
*/
protected function AddParams($sHyperlink, $aExtraParams)
@@ -732,8 +740,7 @@ class MenuGroup extends MenuNode
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -771,8 +778,7 @@ class TemplateMenuNode extends MenuNode
}
/**
* @param $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -781,10 +787,8 @@ class TemplateMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
* @return mixed|void
* @throws DictExceptionMissingString
* @inheritDoc
* @throws \Exception
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -873,12 +877,8 @@ class OQLMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
* @return mixed|void
* @throws CoreException
* @throws DictExceptionMissingString
* @throws OQLException
* @inheritDoc
* @throws \Exception
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -897,11 +897,11 @@ class OQLMenuNode extends MenuNode
}
/**
* @param $sOql
* @param $sTitle
* @param $sUsageId
* @param $bSearchPane
* @param $bSearchOpen
* @param string $sOql
* @param string $sTitle
* @param string $sUsageId
* @param bool $bSearchPane
* @param bool $bSearchOpen
* @param WebPage $oPage
* @param array $aExtraParams
* @param bool $bEnableBreadcrumb
@@ -922,7 +922,7 @@ class OQLMenuNode extends MenuNode
$oBlock->Display($oPage, 0);
}
$oPage->add("<p class=\"page-header\">$sIcon ".Dict::S($sTitle)."</p>");
$oPage->add("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
@@ -974,16 +974,14 @@ class SearchMenuNode extends MenuNode
}
/**
* @param \iTopWebPage $oPage
* @param array $aExtraParams
* @return mixed|void
* @throws DictExceptionMissingString
* @throws Exception
* @inheritDoc
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/search.png');
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/breadcrumb-search.png');
$oSearch = new DBObjectSearch($this->sClass);
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
@@ -1034,8 +1032,7 @@ class WebPageMenuNode extends MenuNode
}
/**
* @param array $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1043,14 +1040,16 @@ class WebPageMenuNode extends MenuNode
return $this->AddParams( $this->sHyperlink, $aExtraParams);
}
/**
* @inheritDoc
*/
public function IsHyperLinkInNewWindow()
{
return $this->bIsLinkInNewWindow;
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -1091,10 +1090,7 @@ class NewObjectMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
*
* @return string
* @throws \Exception
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1128,8 +1124,7 @@ class NewObjectMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param string[] $aExtraParams
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -1167,8 +1162,7 @@ class DashboardMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1187,10 +1181,8 @@ class DashboardMenuNode extends MenuNode
}
/**
* @param \iTopWebPage $oPage
* @param string[] $aExtraParams
* @throws CoreException
* @throws Exception
* @inheritDoc
* @throws \Exception
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -1198,8 +1190,9 @@ class DashboardMenuNode extends MenuNode
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $this->sMenuId);
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
$oPage->add('<div class="dashboard_contents" id="'.$sDivId.'">');
$aExtraParams['dashboard_div_id'] = $sDivId;
$oDashboard->SetReloadURL($this->GetHyperlink($aExtraParams));
$oDashboard->Render($oPage, false, $aExtraParams);
$oPage->add('</div>');
@@ -1284,8 +1277,7 @@ class DashboardMenuNode extends MenuNode
class ShortcutContainerMenuNode extends MenuNode
{
/**
* @param string[] $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1293,15 +1285,14 @@ class ShortcutContainerMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param string[] $aExtraParams
* @return mixed|void
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
}
/**
* @inheritDoc
* @throws CoreException
* @throws Exception
*/
@@ -1356,9 +1347,7 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
* @return string
* @throws CoreException
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1376,10 +1365,8 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param string[] $aExtraParams
* @return mixed|void
* @throws DictExceptionMissingString
* @inheritDoc
* @throws \Exception
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -1388,8 +1375,9 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @return string
* @throws CoreException
* @inheritDoc
*
* @throws \Exception
*/
public function GetTitle()
{
@@ -1397,8 +1385,9 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @return string
* @throws CoreException
* @inheritDoc
*
* @throws \Exception
*/
public function GetLabel()
{

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class NiceWebPage
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT."/application/webpage.class.inc.php");
@@ -46,8 +39,7 @@ class NiceWebPage extends WebPage
{
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.prod.min.js');
}
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/ui-lightness/jquery-ui-1.11.4.custom.css');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.11.4.custom.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
@@ -124,7 +116,7 @@ class NiceWebPage extends WebPage
$("table.listResults").tableHover(); // hover tables
EOF
);
$this->add_saas("css/light-grey.scss");
$this->LoadTheme();
$this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot();
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
@@ -257,6 +249,14 @@ EOF
}
parent::output();
}
}
?>
/**
* @throws \Exception
* @since 2.7.0
*/
protected function LoadTheme()
{
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
$this->add_linked_stylesheet($sCssThemeUrl);
}
}

View File

@@ -40,7 +40,7 @@ class iTopPDF extends TCPDF
*
* @uses \TCPDF::SetFont()
* @uses \iTopPDF::GetPdfFont()
* @since 2.7
* @since 2.7.0
*/
public function SetFontParams($style, $size, $fontfile='', $subset='default', $out=true)
{

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class DisplayTemplate
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
require_once(APPROOT.'/application/displayblock.class.inc.php');
@@ -191,7 +184,7 @@ class DisplayTemplate
break;
case 'itoptab':
$oPage->SetCurrentTab(Dict::S(str_replace('_', ' ', $aAttributes['name'])));
$oPage->SetCurrentTab($aAttributes['name'], str_replace('_', ' ', $aAttributes['name']));
$oTemplate = new DisplayTemplate($sContent);
$oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
//$oPage->p('iTop Tab Content:<pre>'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'</pre>');

View File

@@ -0,0 +1,190 @@
<?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
*/
/**
* Class ThemeHandler
*
* @author Stephen Abello <stephen.abello@combodo.com>
* @since 2.7.0
*/
class ThemeHandler
{
/**
* Return default theme name and parameters
*
* @return array
* @since 2.7.0
*/
public static function GetDefaultThemeInformation()
{
return array(
'name' => 'light-grey',
'parameters' => array(
'variables' => array(),
'imports' => array(
'css-variables' => '../css/css-variables.scss',
),
'stylesheets' => array(
'jqueryui' => '../css/ui-lightness/jqueryui.scss',
'main' => '../css/light-grey.scss',
),
),
);
}
/**
* Return the ID of the theme currently defined in the config. file
*
* @return string
*/
public static function GetCurrentThemeId()
{
try
{
$sThemeId = MetaModel::GetConfig()->Get('backoffice_default_theme');
}
catch(CoreException $oCompileException)
{
// Fallback on our default theme in case the config. is not available yet
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
$sThemeId = $aDefaultTheme['name'];
}
return $sThemeId;
}
/**
* Return the absolute path of the compiled theme folder.
*
* @param string $sThemeId
*
* @return string
*/
public static function GetCompiledThemeFolderAbsolutePath($sThemeId)
{
return APPROOT.'env-'.utils::GetCurrentEnvironment().'/branding/themes/'.$sThemeId.'/';
}
/**
* Return the absolute URL for the current theme CSS file
*
* @return string
* @throws \Exception
*/
public static function GetCurrentThemeUrl()
{
try
{
// Try to compile theme defined in the configuration
$sThemeId = static::GetCurrentThemeId();
static::CompileTheme($sThemeId);
}
catch(CoreException $oCompileException)
{
// Fallback on our default theme (should always be compilable) in case the previous theme doesn't exists
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
$sThemeId = $aDefaultTheme['name'];
$sDefaultThemeDirPath = static::GetCompiledThemeFolderAbsolutePath($sThemeId);
// Create our theme dir if it doesn't exist (XML theme node removed, renamed etc..)
if(!is_dir($sDefaultThemeDirPath))
{
SetupUtils::builddir($sDefaultThemeDirPath);
}
static::CompileTheme($sThemeId, $aDefaultTheme['parameters']);
}
// Return absolute url to theme compiled css
return utils::GetAbsoluteUrlModulesRoot().'/branding/themes/'.$sThemeId.'/main.css';
}
/**
* Compile the $sThemeId theme
*
* @param string $sThemeId
* @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
*/
public static function CompileTheme($sThemeId, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null)
{
// Default working path
if($sWorkingPath === null)
{
$sWorkingPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/';
}
// Default import paths (env-*)
if($aImportsPaths === null)
{
$aImportsPaths = array(
APPROOT.'env-'.utils::GetCurrentEnvironment().'/',
);
}
// Note: We do NOT check that the folder exists!
$sThemeFolderPath = $sWorkingPath.'/branding/themes/'.$sThemeId.'/';
$sThemeCssPath = $sThemeFolderPath.'main.css';
// Save parameters if passed... (typically during DM compilation)
if(is_array($aThemeParameters))
{
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)
else
{
$aThemeParameters = json_decode(@file_get_contents($sThemeFolderPath.'theme-parameters.json'), true);
if ($aThemeParameters === null)
{
throw new CoreException('Could not load "'.$sThemeId.'" theme parameters from file, check that it has been compiled correctly');
}
}
$sTmpThemeScssContent = '';
$iStyleLastModified = 0;
clearstatcache();
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
foreach ($aThemeParameters['imports'] as $sImport)
{
$sTmpThemeScssContent .= '@import "'.$sImport.'";'."\n";
$iImportLastModified = @filemtime($sWorkingPath.$sImport);
$iStyleLastModified = $iStyleLastModified < $iImportLastModified ? $iImportLastModified : $iStyleLastModified;
}
foreach ($aThemeParameters['stylesheets'] as $sStylesheet)
{
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
$iStylesheetLastModified = @filemtime($sWorkingPath.$sStylesheet);
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
// Checking if our compiled css is outdated
if (!file_exists($sThemeCssPath) || (is_writable($sThemeFolderPath) && (@filemtime($sThemeCssPath) < $iStyleLastModified)))
{
$sTmpThemeCssContent = utils::CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths, $aThemeParameters['variables']);
file_put_contents($sThemeCssPath, $sTmpThemeCssContent);
}
}
}

View File

@@ -122,5 +122,19 @@ class TwigExtension
$oConfig = MetaModel::GetConfig();
return $oConfig->Get($sParamName);
}));
// Function to get the URL of a static page in a module
// Usage in twig: {{ get_static_page_module_url('itop-my-module', 'path-to-my-page') }}
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_static_page_module_url', function($sModuleName, $sPage)
{
return utils::GetAbsoluteUrlModulesRoot().$sModuleName.'/'.$sPage;
}));
// Function to get the URL of a php page in a module
// Usage in twig: {{ get_page_module_url('itop-my-module', 'path-to-my-my-page.php') }}
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_page_module_url', function($sModuleName, $sPage)
{
return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage);
}));
}
}
}

View File

@@ -1,23 +1,28 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* 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
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
/**
* Class UIExtKeyWidget
* UI wdiget for displaying and editing external keys when
* UI widget for displaying and editing external keys when
* A simple drop-down list is not enough...
*
* The layout is the following
@@ -54,13 +59,7 @@
* | | +--------+ +-----+ | |
* | +--------------------------------------------+ |
* +------------------------------------------------+
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @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 UIExtKeyWidget
{
const ENUM_OUTPUT_FORMAT_CSV = 'csv';
@@ -274,7 +273,7 @@ EOF
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
$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
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
@@ -298,7 +297,7 @@ EOF
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
$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(
<<<EOF
if ($('#ac_tree_{$this->iId}').length == 0)
@@ -312,7 +311,7 @@ EOF
{
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
$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(
<<<EOF
if ($('#ajax_{$this->iId}').length == 0)
@@ -578,8 +577,21 @@ EOF
$oNewObj->UpdateObjectFromArg('default');
$sDialogTitle = '';
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
$sClassLabel = MetaModel::GetName($this->sTargetClass);
$sClassIcon = MetaModel::GetClassIcon($this->sTargetClass);
$sObjClass = get_class($oNewObj);
$sObjKey = $oNewObj->GetKey();
$sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);
$oPage->add(<<<HTML
<div id="ac_create_{$this->iId}">
<!-- Beginning of object-details -->
<div class="object-details" data-object-class="$sObjClass" data-object-id="$sObjKey" data-object-mode="create">
<!-- Beginning of wizContainer -->
<div class="wizContainer" style="vertical-align:top;">
<div id="dcr_{$this->iId}">
<h1>$sClassIcon&nbsp;$sHeaderTitle</h1>
HTML
);
$aFieldsFlags = array();
$aFieldsComments = array();
foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef)
@@ -591,7 +603,13 @@ EOF
}
}
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
$oPage->add('</div></div></div>');
$oPage->add(<<<HTML
</div>
</div><!-- End of wizContainer -->
</div><!-- End of object-details -->
</div>
HTML
);
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");

View File

@@ -346,7 +346,7 @@ EOF
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
{
$sHtmlValue = '';
$sHtmlValue .= "<div id=\"linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";

View File

@@ -34,7 +34,7 @@ require_once(APPROOT.'/core/userrights.class.inc.php');
*/
class appUserPreferences extends DBObject
{
static $oUserPrefs = null; // Local cache
private static $oUserPrefs = null; // Local cache
/**
* Get the value of the given property/preference
@@ -43,7 +43,7 @@ class appUserPreferences extends DBObject
* @param string $sDefaultValue The default value
* @return string The value of the property for the current user
*/
static function GetPref($sCode, $sDefaultValue)
public static function GetPref($sCode, $sDefaultValue)
{
if (self::$oUserPrefs == null)
{
@@ -65,7 +65,7 @@ class appUserPreferences extends DBObject
* @param string $sCode Code/Name of the property/preference to set
* @param string $sValue Value to set
*/
static function SetPref($sCode, $sValue)
public static function SetPref($sCode, $sValue)
{
if (self::$oUserPrefs == null)
{
@@ -83,13 +83,13 @@ class appUserPreferences extends DBObject
self::Save();
}
}
/**
* Clears the value for a given preference (or list of preferences that matches a pattern), and updates the database
* @param string $sPattern Code/Pattern of the properties/preferences to reset
* @param string $sCodeOrPattern Code/Pattern of the properties/preferences to reset
* @param boolean $bPattern Whether or not the supplied code is a PCRE pattern
*/
static function UnsetPref($sCodeOrPattern, $bPattern = false)
public static function UnsetPref($sCodeOrPattern, $bPattern = false)
{
if (self::$oUserPrefs == null)
{
@@ -124,7 +124,7 @@ class appUserPreferences extends DBObject
* Call this function to get all the preferences for the user, packed as a JSON object
* @return string JSON representation of the preferences
*/
static function GetAsJSON()
public static function GetAsJSON()
{
if (self::$oUserPrefs == null)
{
@@ -137,19 +137,19 @@ class appUserPreferences extends DBObject
/**
* Call this function if the user has changed (like when doing a logoff...)
*/
static public function ResetPreferences()
public static function ResetPreferences()
{
self::$oUserPrefs = null;
}
/**
* Call this function to ERASE all the preferences from the current user
*/
static public function ClearPreferences()
public static function ClearPreferences()
{
self::$oUserPrefs = null;
}
static protected function Save()
protected static function Save()
{
if (self::$oUserPrefs != null)
{
@@ -166,7 +166,7 @@ class appUserPreferences extends DBObject
* Loads the preferences for the current user, creating the record in the database
* if needed
*/
static protected function Load()
protected static function Load()
{
if (self::$oUserPrefs != null) return;
$oSearch = new DBObjectSearch('appUserPreferences');

View File

@@ -1,6 +1,6 @@
<?php
/**
* Copyright (C) 2013-2019 Combodo SARL
* Copyright (C) 2013-2020 Combodo SARL
*
* This file is part of iTop.
*
@@ -275,13 +275,14 @@ class utils
/**
* @param string|string[] $value
* @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name,
* transaction_id, parameter, raw_data
* element_identifier, transaction_id, parameter, raw_data
*
* @return string|string[]|bool boolean for :
* * the 'class' filter (true if valid, false otherwise)
* * if the filter fails (@see \filter_var())
*
* @since 2.5.2 2.6.0 new 'transaction_id' filter
* @since 2.7.0 new 'element_identifier' filter
*/
protected static function Sanitize_Internal($value, $sSanitizationFilter)
{
@@ -351,6 +352,11 @@ class utils
}
break;
// For XML / HTML node identifiers
case 'element_identifier':
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
break;
default:
case 'raw_data':
$retValue = $value;
@@ -591,9 +597,11 @@ class utils
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
*
* @param float $value
* @param int $iPrecision
*
* @return string
*/
public static function BytesToFriendlyFormat($value)
public static function BytesToFriendlyFormat($value, $iPrecision = 0)
{
$sReturn = '';
// Kilobytes
@@ -601,6 +609,9 @@ class utils
{
$sReturn = 'K';
$value = $value / 1024;
if ($iPrecision === 0) {
$iPrecision = 1;
}
}
// Megabytes
if ($value >= 1024)
@@ -621,16 +632,18 @@ class utils
$value = $value / 1024;
}
$value = round($value, 1);
$value = round($value, $iPrecision);
return $value . '' . $sReturn . 'B';
}
/**
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
* Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for
* specifying a date in a french format (for instance) Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
*
* @param string $sDate
* @param string $sFormat
*
* @return string|false false if the input format is not correct, timestamp otherwise
*/
public static function StringToTime($sDate, $sFormat)
@@ -790,13 +803,14 @@ class utils
return $sUrl;
}
/**
* Builds an root url from the server's variables.
* For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the app_root_url conf parameter is not defined.
*
* @return string
*
* @throws \Exception
/**
* Builds an root url from the server's variables.
* For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the
* app_root_url conf parameter is not defined.
*
* @return string
*
* @throws \Exception
*/
public static function GetDefaultUrlAppRoot()
{
@@ -838,10 +852,25 @@ class utils
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sAppRoot = realpath(APPROOT);
return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl);
}
/**
* @param $sCurrentScript
* @param $sAppRoot
* @param $sAbsoluteUrl
*
* @return false|string
* @throws \Exception
*/
public static function GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl)
{
$sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path
$sAppRoot = str_replace('\\', '/', APPROOT); // canonical path
$sCurrentRelativePath = str_replace($sAppRoot, '', $sCurrentScript);
$sAppRoot = str_replace('\\', '/', $sAppRoot).'/'; // canonical path with the trailing '/' appended
$sCurrentRelativePath = str_ireplace($sAppRoot, '', $sCurrentScript);
$sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
if ($sAppRootPos !== false)
{
@@ -850,7 +879,7 @@ class utils
else
{
// Second attempt without index.php at the end...
$sCurrentRelativePath = str_replace('index.php', '', $sCurrentRelativePath);
$sCurrentRelativePath = str_ireplace('index.php', '', $sCurrentRelativePath);
$sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
if ($sAppRootPos !== false)
{
@@ -860,8 +889,9 @@ class utils
{
// No luck...
throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'");
}
}
}
return $sAppRootUrl;
}
@@ -1144,16 +1174,17 @@ class utils
break;
default:
// Unknown type of menu, do nothing
$aResult = array();
// Unknown type of menu, do nothing
$aResult = array();
}
foreach($aResult as $oMenuItem)
foreach ($aResult as $oMenuItem)
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
}
// Invoke the plugins
//
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
if (is_object($param) && !($param instanceof DBObject))
@@ -1480,6 +1511,17 @@ class utils
public static function HtmlEntities($sValue)
{
return htmlentities($sValue, ENT_QUOTES, 'UTF-8');
}
/**
* Helper to encapsulation iTop's html_entity_decode
* @param string $sValue
* @return string
* @since 2.7.0
*/
public static function HtmlEntityDecode($sValue)
{
return html_entity_decode($sValue, ENT_QUOTES, 'UTF-8');
}
/**
@@ -1541,18 +1583,40 @@ class utils
clearstatcache();
if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath))))
{
$oScss = new Compiler();
$oScss->setImportPaths($aImportPaths);
$oScss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded');
// Temporary disabling max exec time while compiling
$iCurrentMaxExecTime = (int) ini_get('max_execution_time');
set_time_limit(0);
$sCss = $oScss->compile(file_get_contents($sSassPath));
set_time_limit($iCurrentMaxExecTime);
$sCss = static::CompileCSSFromSASS(file_get_contents($sSassPath), $aImportPaths);
file_put_contents($sCssPath, $sCss);
}
return $sCssRelPath;
}
/**
* Return a string of CSS compiled from the $sSassContent
*
* @param string $sSassContent
* @param array $aImportPaths
* @param array $aVariables
*
* @return string
*
* @since 2.7.0
*/
public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array())
{
$oSass = new Compiler();
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded');
// Setting our variables
$oSass->setVariables($aVariables);
// Setting our imports paths
$oSass->setImportPaths($aImportPaths);
// Temporary disabling max exec time while compiling
$iCurrentMaxExecTime = (int) ini_get('max_execution_time');
set_time_limit(0);
// Compiling SASS
$sCss = $oSass->compile($sSassContent);
set_time_limit($iCurrentMaxExecTime);
return $sCss;
}
public static function GetImageSize($sImageData)
{
@@ -1890,17 +1954,17 @@ class utils
'doc' => 'application/msword',
'dot' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'vsd' => 'application/x-visio',
'vdx' => 'application/visio.drawing',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'zip' => 'application/zip',
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'exe' => 'application/octet-stream'
'ppt' => 'application/vnd.ms-powerpoint',
'vsd' => 'application/x-visio',
'vdx' => 'application/visio.drawing',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'zip' => 'application/zip',
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'exe' => 'application/octet-stream',
);
$sData = null;
@@ -2084,6 +2148,11 @@ class utils
*/
final public static function StartsWith($haystack, $needle)
{
if (strlen($needle) > strlen($haystack))
{
return false;
}
return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
@@ -2095,6 +2164,11 @@ class utils
* @return bool
*/
final public static function EndsWith($haystack, $needle) {
if (strlen($needle) > strlen($haystack))
{
return false;
}
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
@@ -2131,14 +2205,14 @@ class utils
* Returns the local path relative to the iTop installation of an existing file
* Dir separator is changed to '/' for consistency among the different OS
*
* @param string $sPath absolute path
* @param string $sAbsolutePath absolute path
*
* @return false|string
*/
final public static function LocalPath($sPath)
final public static function LocalPath($sAbsolutePath)
{
$sRootPath = realpath(APPROOT);
$sFullPath = realpath($sPath);
$sFullPath = realpath($sAbsolutePath);
if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath))
{
return false;
@@ -2165,4 +2239,32 @@ class utils
}
return $sFullPath;
}
public static function GetAbsoluteModulePath($sModule)
{
return APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$sModule.'/';
}
public static function GetCurrentUserName()
{
if (function_exists('posix_getpwuid'))
{
return posix_getpwuid(posix_geteuid())['name'];
}
return getenv('username');
}
/**
* Transform a snake_case $sInput into a CamelCase string
*
* @since 2.7.0
* @param string $sInput
*
* @return string
*/
public static function ToCamelCase($sInput)
{
return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' ')));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -273,10 +273,6 @@ class WizardHelper
static public function FromJSON($sJSON)
{
$oWizHelper = new WizardHelper();
if (get_magic_quotes_gpc())
{
$sJSON = stripslashes($sJSON);
}
$aData = json_decode($sJSON, true); // true means hash array instead of object
$oWizHelper->m_aData = $aData;
return $oWizHelper;

View File

@@ -174,7 +174,7 @@ Class XLSXWriter
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
} else if ($value==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
} else if ($value{0}=='='){
} else if ($value[0]=='='){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><f>'.self::xmlspecialchars($value).'</f></c>');
} else if ($value!==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><v>'.self::xmlspecialchars($this->setSharedString($value)).'</v></c>');

View File

@@ -2,67 +2,5 @@
define('APPROOT', dirname(__FILE__).'/');
define('APPCONF', APPROOT.'conf/');
define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
if (function_exists('microtime'))
{
$fItopStarted = microtime(true);
}
else
{
$fItopStarted = 1000 * time();
}
//
// Maintenance mode
//
// Use 'maintenance' parameter to bypass maintenance mode
if (!isset($bBypassMaintenance))
{
$bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false;
}
if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
{
$sMessage = 'This application is currently under maintenance.';
$sTitle = 'Maintenance';
http_response_code(503);
// Display message depending on the request
include(APPROOT.'application/maintenancemsg.php');
switch (true)
{
case isset($_SERVER['REQUEST_URI']) && EndsWith($_SERVER['REQUEST_URI'], '/pages/ajax.searchform.php'):
_MaintenanceHtmlMessage($sMessage);
break;
case array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER):
case isset($_SERVER['REQUEST_URI']) && EndsWith($_SERVER['REQUEST_URI'], '/webservices/soapserver.php'):
case isset($_SERVER['REQUEST_URI']) && EndsWith($_SERVER['REQUEST_URI'], '/webservices/rest.php'):
_MaintenanceTextMessage($sMessage);
break;
case isset($_SERVER['CONTENT_TYPE']) && ($_SERVER['CONTENT_TYPE'] == 'application/json'):
_MaintenanceJsonMessage($sTitle, $sMessage);
break;
default:
_MaintenanceSetupPageMessage($sTitle, $sMessage);
break;
}
exit();
}
/**
* helper to test if a string ends with another
* @param $haystack
* @param $needle
*
* @return bool
*/
function EndsWith($haystack, $needle) {
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
require_once APPROOT.'bootstrap.inc.php';

View File

@@ -18,7 +18,76 @@
* You should have received a copy of the GNU Affero General Public License
*/
require_once __DIR__.'/approot.inc.php';
require_once APPROOT.'/lib/autoload.php';
define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
// Require here files containing PHP instructions
if (function_exists('microtime'))
{
$fItopStarted = microtime(true);
}
else
{
$fItopStarted = 1000 * time();
}
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false)
{
require_once APPROOT.'/lib/autoload.php';
}
//
// Maintenance mode
//
// Use 'maintenance' parameter to bypass maintenance mode
if (!isset($bBypassMaintenance))
{
$bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false;
}
if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
{
$sTitle = 'Maintenance';
$sMessage = 'This application is currently under maintenance.';
http_response_code(503);
// Display message depending on the request
include(APPROOT.'application/maintenancemsg.php');
$sSAPIName = strtoupper(trim(php_sapi_name()));
switch (true)
{
case isset($_SERVER['REQUEST_URI']) && EndsWith($_SERVER['REQUEST_URI'], '/pages/ajax.searchform.php'):
_MaintenanceHtmlMessage($sMessage);
break;
case $sSAPIName == 'CLI':
case array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER):
case isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '/webservices/soapserver.php') !== false):
case isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '/webservices/export-v2.php') !== false):
_MaintenanceTextMessage($sMessage);
break;
case isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '/webservices/rest.php') !== false):
case isset($_SERVER['CONTENT_TYPE']) && ($_SERVER['CONTENT_TYPE'] == 'application/json'):
_MaintenanceJsonMessage($sTitle, $sMessage);
break;
default:
_MaintenanceSetupPageMessage($sTitle, $sMessage);
break;
}
exit();
}
/**
* helper to test if a string ends with another
* @param $haystack
* @param $needle
*
* @return bool
*/
function EndsWith($haystack, $needle) {
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

View File

@@ -3,26 +3,25 @@
"license": "AGPLv3",
"require": {
"php": ">=5.6.0",
"ext-soap": "*",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-mysqli": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-gd": "*",
"ext-ctype": "*",
"scssphp/scssphp": "1.0.0",
"swiftmailer/swiftmailer": "5.4.9",
"ext-soap": "*",
"combodo/tcpdf": "6.3.4",
"nikic/php-parser": "^3.1",
"pear/archive_tar": "1.4.9",
"pelago/emogrifier": "2.1.0",
"combodo/tcpdf": "6.3.0",
"pear/archive_tar": "1.4.7",
"scssphp/scssphp": "1.0.6",
"swiftmailer/swiftmailer": "5.4.12",
"symfony/console": "3.4.*",
"symfony/dotenv": "3.4.*",
"symfony/framework-bundle": "3.4.*",
"symfony/polyfill-php70": "1.*",
"symfony/twig-bundle": "3.4.*",
"symfony/yaml": "3.4.*",
"symfony/polyfill-php70": "1.*"
"symfony/yaml": "3.4.*"
},
"require-dev": {
"symfony/stopwatch": "3.4.*",
@@ -44,12 +43,15 @@
"preferred-install": {
"*": "dist"
},
"sort-packages": true
"sort-packages": true,
"classmap-authoritative": true
},
"autoload": {
"classmap": [
"core",
"application"
"application",
"sources/application",
"sources/Composer"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",
@@ -73,5 +75,10 @@
"allow-contrib": false,
"require": "3.4.*"
}
},
"scripts": {
"post-install-cmd": ["@rmDeniedTestDir"],
"post-update-cmd": ["@rmDeniedTestDir"],
"rmDeniedTestDir": "@php .make/composer/rmDeniedTestDir.php"
}
}
}

626
composer.lock generated

File diff suppressed because it is too large Load Diff

2
composer.readme Normal file
View File

@@ -0,0 +1,2 @@
To regenerate the autoload, run:
composer dump-autoload -a

View File

@@ -457,7 +457,7 @@ class Str
public static function gpc2pure($gpc)
{
if (ini_get('magic_quotes_sybase')) $pure = str_replace("''", "'", $gpc);
else $pure = get_magic_quotes_gpc() ? stripslashes($gpc) : $gpc;
else $pure = $gpc;
return $pure;
}
public static function html2pure($html)

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ MetaModel::IncludeModule('core/tagsetfield.class.inc.php');
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
MetaModel::IncludeModule('core/counter.class.inc.php');
MetaModel::IncludeModule('webservices/webservices.basic.php');

View File

@@ -56,8 +56,9 @@ interface iBackgroundProcess extends iProcess
* interface iScheduledProcess
* A variant of process that must be called at specific times
*
* @copyright Copyright (C) 2013 Combodo SARL
* @see \AbstractWeeklyScheduledProcess for a bootstrap implementation
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2013 Combodo SARL
*/
interface iScheduledProcess extends iProcess
{
@@ -67,14 +68,189 @@ interface iScheduledProcess extends iProcess
public function GetNextOccurrence();
}
/**
* Class ProcessException
* Exception for iProcess implementations.<br>
* Implementation of {@link iScheduledProcess}, using config parameters for module
*
* Use these parameters :
*
* * enabled
* * week_days
* * time
*
* Param names and some of their default values are in constant that can be overriden.
*
* Other info (module name and time default value) should be provided using a method that needs to be implemented.
*
* @since 2.7.0
*/
abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
{
// param have default names/values but can be overriden
const MODULE_SETTING_ENABLED = 'enabled';
const DEFAULT_MODULE_SETTING_ENABLED = true;
const MODULE_SETTING_WEEKDAYS = 'week_days';
const DEFAULT_MODULE_SETTING_WEEKDAYS = 'monday, tuesday, wednesday, thursday, friday, saturday, sunday';
const MODULE_SETTING_TIME = 'time';
/**
* Module must be declared in each implementation
*
* @return string
*/
abstract protected function GetModuleName();
/**
* @return string default value for {@link MODULE_SETTING_TIME} config param.
* example '23:30'
*/
abstract protected function GetDefaultModuleSettingTime();
/**
* Interpret current setting for the week days
*
* @returns int[] (monday = 1)
* @throws ProcessInvalidConfigException
*/
public function InterpretWeekDays()
{
static $aWEEKDAYTON = array(
'monday' => 1,
'tuesday' => 2,
'wednesday' => 3,
'thursday' => 4,
'friday' => 5,
'saturday' => 6,
'sunday' => 7,
);
$aDays = array();
$sWeekDays = MetaModel::GetConfig()->GetModuleSetting(
$this->GetModuleName(),
static::MODULE_SETTING_WEEKDAYS,
static::DEFAULT_MODULE_SETTING_WEEKDAYS
);
if ($sWeekDays !== '')
{
$aWeekDaysRaw = explode(',', $sWeekDays);
foreach ($aWeekDaysRaw as $sWeekDay)
{
$sWeekDay = strtolower(trim($sWeekDay));
if (array_key_exists($sWeekDay, $aWEEKDAYTON))
{
$aDays[] = $aWEEKDAYTON[$sWeekDay];
}
else
{
throw new ProcessInvalidConfigException($this->GetModuleName().": wrong format for setting '".static::MODULE_SETTING_WEEKDAYS."' (found '$sWeekDay')");
}
}
}
if (count($aDays) === 0)
{
throw new ProcessInvalidConfigException($this->GetModuleName().': missing setting \''.static::MODULE_SETTING_WEEKDAYS.'\'');
}
$aDays = array_unique($aDays);
sort($aDays);
return $aDays;
}
/**
* Gives the exact time at which the process must be run next time
*
* @return DateTime
* @throws Exception
*/
public function GetNextOccurrence()
{
$bEnabled = MetaModel::GetConfig()->GetModuleSetting(
$this->GetModuleName(),
static::MODULE_SETTING_ENABLED,
static::DEFAULT_MODULE_SETTING_ENABLED
);
if (!$bEnabled)
{
return new DateTime('3000-01-01');
}
// 1st - Interpret the list of days as ordered numbers (monday = 1)
//
$aDays = $this->InterpretWeekDays();
// 2nd - Find the next active week day
//
$sProcessTime = MetaModel::GetConfig()->GetModuleSetting(
$this->GetModuleName(),
static::MODULE_SETTING_TIME,
static::GetDefaultModuleSettingTime()
);
if (!preg_match('/[0-2]\d:[0-5]\d/', $sProcessTime))
{
throw new ProcessInvalidConfigException($this->GetModuleName().": wrong format for setting '".static::MODULE_SETTING_TIME."' (found '$sProcessTime')");
}
$oNow = new DateTime();
$iNextPos = false;
for ($iDay = $oNow->format('N'); $iDay <= 7; $iDay++)
{
$iNextPos = array_search($iDay, $aDays, true);
if ($iNextPos !== false)
{
if (($iDay > $oNow->format('N')) || ($oNow->format('H:i') < $sProcessTime))
{
break;
}
$iNextPos = false; // necessary on sundays
}
}
// 3rd - Compute the result
//
if ($iNextPos === false)
{
// Jump to the first day within the next week
$iFirstDayOfWeek = $aDays[0];
$iDayMove = $oNow->format('N') - $iFirstDayOfWeek;
$oRet = clone $oNow;
$oRet->modify('-'.$iDayMove.' days');
$oRet->modify('+1 weeks');
}
else
{
$iNextDayOfWeek = $aDays[$iNextPos];
$iMove = $iNextDayOfWeek - $oNow->format('N');
$oRet = clone $oNow;
$oRet->modify('+'.$iMove.' days');
}
list($sHours, $sMinutes) = explode(':', $sProcessTime);
$oRet->setTime((int)$sHours, (int)$sMinutes);
return $oRet;
}
/**
* @see \iProcess
*
* @param int $iUnixTimeLimit
*
* @return string
*/
abstract public function Process($iUnixTimeLimit);
}
/**
* Exception for {@link iProcess} implementations.<br>
* An error happened during the processing but we can go on with the next implementations.
*/
class ProcessException extends CoreException
{
}
/**
* @since 2.7.0
*/
class ProcessInvalidConfigException extends ProcessException
{
}
/**
@@ -84,5 +260,4 @@ class ProcessException extends CoreException
*/
class ProcessFatalException extends CoreException
{
}

View File

@@ -654,7 +654,7 @@ class BulkChange
return $aResults;
}
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
{
$oTargetObj = MetaModel::NewObject($this->m_sClass);
@@ -726,25 +726,38 @@ class BulkChange
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-MissingExtKey', $sMissingKeys));
return $oTargetObj;
}
// Optionaly record the results
// Optionally record the results
//
if ($oChange)
{
$newID = $oTargetObj->DBInsertTrackedNoReload($oChange);
$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj();
$aResult[$iRow]["finalclass"] = get_class($oTargetObj);
$aResult[$iRow]["id"] = new CellStatus_Void($newID);
$newID = $oTargetObj->DBInsert();
}
else
{
$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj();
$aResult[$iRow]["finalclass"] = get_class($oTargetObj);
$aResult[$iRow]["id"] = new CellStatus_Void(0);
$newID = 0;
}
$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj();
$aResult[$iRow]["finalclass"] = get_class($oTargetObj);
$aResult[$iRow]["id"] = new CellStatus_Void($newID);
return $oTargetObj;
}
/**
* @param array $aResult
* @param int $iRow
* @param \CMDBObject $oTargetObj
* @param array $aRowData
* @param \CMDBChange $oChange
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
@@ -772,7 +785,7 @@ class BulkChange
{
try
{
$oTargetObj->DBUpdateTracked($oChange);
$oTargetObj->DBUpdate();
}
catch(CoreException $e)
{
@@ -786,6 +799,14 @@ class BulkChange
}
}
/**
* @param array $aResult
* @param int $iRow
* @param \CMDBObject $oTargetObj
* @param \CMDBChange $oChange
*
* @throws \BulkChangeException
*/
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors);
@@ -813,7 +834,7 @@ class BulkChange
{
try
{
$oTargetObj->DBUpdateTracked($oChange);
$oTargetObj->DBUpdate();
}
catch(CoreException $e)
{
@@ -829,6 +850,11 @@ class BulkChange
public function Process(CMDBChange $oChange = null)
{
if ($oChange)
{
CMDBObject::SetCurrentChange($oChange);
}
// Note: $oChange can be null, in which case the aim is to check what would be done
// Debug...

View File

@@ -1,20 +1,21 @@
<?php
// Copyright (C) 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/>
/**
* 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
*/
define('EXPORTER_DEFAULT_CHUNK_SIZE', 1000);
@@ -109,7 +110,7 @@ class BulkExportResultGC implements iBackgroundProcess
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
$oSet->OptimizeColumnLoad(array('temp_file_path'));
$oSet->OptimizeColumnLoad(array('BulkExportResult' => array('temp_file_path')));
$oResult = $oSet->Fetch();
if (is_null($oResult))
{
@@ -170,6 +171,7 @@ abstract class BulkExport
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubclassOf('BulkExport') && !$oRefClass->isAbstract())
{
/** @var BulkExport $oBulkExporter */
$oBulkExporter = new $sPHPClass();
if ($oBulkExporter->IsFormatSupported($sFormatCode, $oSearch))
{
@@ -189,7 +191,7 @@ abstract class BulkExport
*
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
*
* @return iBulkExport|null
* @return BulkExport|null
* @throws ArchivedObjectException
* @throws CoreException
* @throws ReflectionException

View File

@@ -513,7 +513,23 @@ 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);
@@ -521,7 +537,24 @@ abstract class CMDBObject extends DBObject
$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);
@@ -529,13 +562,20 @@ abstract class CMDBObject extends DBObject
$ret = $this->DBInsertTracked_Internal(true);
return $ret;
}
/**
* To Be Obsoleted: DO NOT rely on an overload of this method since
* DBInsertTracked (resp. DBInsertTrackedNoReload) may call directly
* DBInsert (resp. DBInsertNoReload) in future versions of iTop.
* @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)
{
@@ -582,6 +622,18 @@ 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);
@@ -593,13 +645,38 @@ abstract class CMDBObject extends DBObject
* @param null $oDeletionPlan
*
* @return \DeletionPlan|null
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function DBDelete(&$oDeletionPlan = null)
{
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);
@@ -611,11 +688,17 @@ abstract class CMDBObject extends DBObject
* @param null $oDeletionPlan
*
* @return \DeletionPlan|null
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
$prevkey = $this->GetKey();
$ret = parent::DBDelete($oDeletionPlan);
return $ret;
}

View File

@@ -64,7 +64,7 @@ class MySQLException extends CoreException
/**
* Class MySQLQueryHasNoResultException
*
* @since 2.5
* @since 2.5.0
*/
class MySQLQueryHasNoResultException extends MySQLException
{
@@ -74,7 +74,7 @@ class MySQLQueryHasNoResultException extends MySQLException
/**
* Class MySQLHasGoneAwayException
*
* @since 2.5
* @since 2.5.0
* @see itop bug 1195
* @see https://dev.mysql.com/doc/refman/5.7/en/gone-away.html
*/
@@ -126,12 +126,12 @@ class CMDBSource
protected static $m_sDBName;
/**
* @var boolean
* @since 2.5 N°1260 MySQL TLS first implementation
* @since 2.5.0 N°1260 MySQL TLS first implementation
*/
protected static $m_bDBTlsEnabled;
/**
* @var string
* @since 2.5 N°1260 MySQL TLS first implementation
* @since 2.5.0 N°1260 MySQL TLS first implementation
*/
protected static $m_sDBTlsCA;
@@ -576,11 +576,6 @@ class CMDBSource
return $aRes;
}
// Stripslashes
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number or a numeric string
if ($bAlways || is_string($value))
{
@@ -589,6 +584,24 @@ class CMDBSource
return $value;
}
/**
* MariaDB returns "'value'" for enum, while MySQL returns "value" (without the surrounding single quotes)
*
* @param string $sValue
*
* @return string without the surrounding quotes
* @since 2.7.0 N°2490
*/
private static function RemoveSurroundingQuotes($sValue)
{
if (utils::StartsWith($sValue, '\'') && utils::EndsWith($sValue, '\''))
{
$sValue = substr($sValue, 1, -1);
}
return $sValue;
}
/**
* @param string $sSQLQuery
*
@@ -793,6 +806,9 @@ class CMDBSource
}
/**
*
* @deprecated 2.7.0 N°1627 use ItopCounter instead
*
* @param string $sTable
*
* @return int
@@ -1110,6 +1126,83 @@ class CMDBSource
return false;
}
/**
* There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT 'NULL'"
*
* We still do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
* @param string $sItopGeneratedFieldType
* @param string $sDbFieldType
*
* @return bool true if same type and options (case sensitive comparison only for type options), false otherwise
* @since 2.7.0 N°2490
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
{
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
{
return false;
}
if (strcmp($sItopFieldTypeOptions, $sDbFieldTypeOptions) !== 0)
{
// case sensitive comp as we need to check case for enum possible values for example
return false;
}
// remove the default value NULL added by MariadDB
$sMariaDbDefaultNull = ' DEFAULT \'NULL\'';
if (utils::EndsWith($sDbFieldOtherOptions, $sMariaDbDefaultNull))
{
$sDbFieldOtherOptions = substr($sDbFieldOtherOptions, 0, -strlen($sMariaDbDefaultNull));
}
// remove quotes around default values (always present in MariaDB)
$sDbFieldOtherOptions = preg_replace_callback(
'/( DEFAULT )\'([^\']+)\'/',
function ($aMatches) use ($sItopFieldDataType) {
// ENUM default values should keep quotes, but all other numeric values don't have quotes
if (is_numeric($aMatches[2]) && ($sItopFieldDataType !== 'ENUM'))
{
return $aMatches[1].$aMatches[2];
}
return $aMatches[0];
},
$sDbFieldOtherOptions);
if (strcasecmp($sItopFieldOtherOptions, $sDbFieldOtherOptions) !== 0)
{
return false;
}
return true;
}
/**
* @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
*
* @return string[] consisting of 3 items :
* 1. data type : for example 'VARCHAR'
* 2. type value : for example '255'
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
*/
private static function GetFieldDataTypeAndOptions($sCompleteFieldType)
{
preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?$/', $sCompleteFieldType, $aMatches);
$sDataType = $aMatches[1];
$sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : '';
$sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : '';
return array($sDataType, $sTypeOptions, $sOtherOptions);
}
/**
* @param string $sTable
* @param string $sField
@@ -1161,7 +1254,8 @@ class CMDBSource
}
elseif (is_string($aFieldData["Default"]) == 'string')
{
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]);
$sDefaultValue = static::RemoveSurroundingQuotes($aFieldData["Default"]);
$sRet .= ' DEFAULT '.self::Quote($sDefaultValue);
}
return $sRet;
@@ -1296,7 +1390,7 @@ class CMDBSource
* @return string query to upgrade table charset and collation if needed, null if not
* @throws \MySQLException
*
* @since 2.5 N°1001 switch to utf8mb4
* @since 2.5.0 N°1001 switch to utf8mb4
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-table.html
*/
public static function DBCheckTableCharsetAndCollation($sTableName)
@@ -1446,7 +1540,7 @@ class CMDBSource
* @return string query to upgrade database charset and collation if needed, null if not
* @throws \MySQLException
*
* @since 2.5 N°1001 switch to utf8mb4
* @since 2.5.0 N°1001 switch to utf8mb4
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
*/
public static function DBCheckCharsetAndCollation()

View File

@@ -1,35 +1,44 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Copyright (C) 2010-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
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Any extension to compute things like a stop watch deadline or working hours
* Any extension to compute things like a stop watch deadline or working hours
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Metric computing for stop watches
* Metric computing for stop watches.
* Can be used for AttributeStopWatch goal (iTop XML node xpath: /itop_design/classes/class/fields/field/goal)
*/
interface iMetricComputer
{
public static function GetDescription();
/**
* @param \DBObject $oObject
*
* @return float number of seconds for the time limit
*/
public function ComputeMetric($oObject);
}
@@ -41,21 +50,21 @@ interface iWorkingTimeComputer
public static function GetDescription();
/**
* Get the date/time corresponding to a given delay in the future from the present
* considering only the valid (open) hours for a specified object
* @param $oObject DBObject The object for which to compute the deadline
* @param $iDuration integer The duration (in seconds) in the future
* @param $oStartDate DateTime The starting point for the computation
* @return DateTime The date/time for the deadline
* @param DBObject $oObject The object for which to compute the deadline
* @param integer $iDuration The duration (in seconds) in the future
* @param DateTime $oStartDate The starting point for the computation
*
* @return DateTime The date/time corresponding to a given delay in the future from the present
* considering only the valid (open) hours for a specified object
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate);
/**
* Get duration (considering only open hours) elapsed bewteen two given DateTimes
* @param $oObject DBObject The object for which to compute the duration
* @param $oStartDate DateTime The starting point for the computation (default = now)
* @param $oEndDate DateTime The ending point for the computation (default = now)
* @return integer The duration (number of seconds) of open hours elapsed between the two dates
* @param DBObject $oObject The object for which to compute the duration
* @param DateTime $oStartDate The starting point for the computation (default = now)
* @param DateTime $oEndDate The ending point for the computation (default = now)
*
* @return integer The duration (number of seconds) elapsed between two given dates, considering only open hours
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate);
}
@@ -87,12 +96,7 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
}
/**
* Get the date/time corresponding to a given delay in the future from the present
* considering only the valid (open) hours for a specified object
* @param $oObject DBObject The object for which to compute the deadline
* @param $iDuration integer The duration (in seconds) in the future
* @param $oStartDate DateTime The starting point for the computation
* @return DateTime The date/time for the deadline
* @inheritDoc
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate)
{
@@ -113,11 +117,7 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
}
/**
* Get duration (considering only open hours) elapsed bewteen two given DateTimes
* @param $oObject DBObject The object for which to compute the duration
* @param $oStartDate DateTime The starting point for the computation (default = now)
* @param $oEndDate DateTime The ending point for the computation (default = now)
* @return integer The duration (number of seconds) of open hours elapsed between the two dates
* @inheritDoc
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
{
@@ -134,6 +134,3 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
return $iDuration;
}
}
?>

View File

@@ -25,6 +25,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
define('ITOP_VERSION', '2.7.0-dev');
define('ITOP_REVISION', 'svn');
define('ITOP_BUILD_DATE', '$WCNOW$');
define('ITOP_VERSION_FULL', ITOP_VERSION.'-'.ITOP_REVISION);
define('ACCESS_USER_WRITE', 1);
define('ACCESS_ADMIN_WRITE', 2);
@@ -70,14 +71,14 @@ define('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|external|basic');
define('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']');
define('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random generated key later (if possible)
define('DEFAULT_ENCRYPTION_LIB', 'Mcrypt'); // We'll define the best encryption available later
/**
* Config
* configuration data (this class cannot not be localized, because it is responsible for loading the dictionaries)
*
* @package iTopORM
*
* @see \MetaModel::GetConfig() to get the config, if the metamodel was already loaded
* @see utils::GetConfig() to load config from the current env, if metamodel is not loaded
* @package iTopORM
*/
class Config
{
@@ -89,7 +90,16 @@ class Config
protected $m_aWebServiceCategories;
protected $m_aAddons;
/** @var ConfigPlaceholdersResolver */
private $oConfigPlaceholdersResolver;
protected $m_aModuleSettings;
/**
* @var \iTopConfigParser|null
*/
private $oItopConfigParser;
//for each conf entry, whether the non interpreted value can be kept in case is is written back to the disk.
private $m_aCanOverrideSettings;
/**
* New way to store the settings !
@@ -99,6 +109,14 @@ class Config
* @since 2.7.0 export_pdf_font param
*/
protected $m_aSettings = array(
'log_level_min' => array(
'type' => 'array',
'description' => 'Optional min log level per channel',
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'app_env_label' => array(
'type' => 'string',
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
@@ -343,12 +361,12 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'export_pdf_font' => array( // @since 2.7 PR #49
'export_pdf_font' => array( // @since 2.7.0 PR #49 / N°1947
'type' => 'string',
'description' => 'Font used when generating a PDF file',
'default' => 'DejaVuSans', // DejaVuSans is a UTF-8 Unicode font, embedded in the TCPPDF lib we're using
// Standard PDF fonts like helvetica or times newroman are NOT Unicode
// A new DroidSansFallback can be used to improve CJK support (se PR #49)
// Standard PDF fonts like helvetica or times newroman are NOT Unicode
// A new DroidSansFallback can be used to improve CJK support (se PR #49)
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
@@ -387,8 +405,8 @@ class Config
),
'log_filename_builder_impl' => array(
'type' => 'string',
'description' => 'Name of the ILogFileNameBuilder to use',
'default' => 'WeeklyRotatingLogFileNameBuilder',
'description' => 'Name of the iLogFileNameBuilder to use',
'default' => 'MonthlyRotatingLogFileNameBuilder',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
@@ -588,7 +606,7 @@ class Config
'Asia/Istanbul',
'Asia/Singapore',
'Africa/Casablanca',
'Australia/Sydney'
'Australia/Sydney',
),
'default' => 'Europe/Paris',
'value' => 'Europe/Paris',
@@ -831,46 +849,46 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of an eMail address',
'default' => "[a-zA-Z0-9._&'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}",
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_decoration_class' => array(
'type' => 'string',
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fas fa-envelope" will put a mail icon.',
'default' => 'fas fa-envelope',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of a phone number',
'default' => "[0-9.\-\ \+\(\)]+",
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_url_pattern' => array(
'type' => 'string',
'description' => 'Format for phone number url, use %1$s as a placeholder for the value. eg. "tel:%1$s" for regular phone applications or "callto:%1$s" for Skype. Default is "tel:%1$s".',
'default' => 'tel:%1$s',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_decoration_class' => array(
'type' => 'string',
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fas fa-phone" will put a phone icon.',
'default' => 'fas fa-phone',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of an eMail address',
'default' => "[a-zA-Z0-9._&'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}",
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_decoration_class' => array(
'type' => 'string',
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fas fa-envelope" will put a mail icon.',
'default' => 'fas fa-envelope',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of a phone number',
'default' => "[0-9.\-\ \+\(\)]+",
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_url_pattern' => array(
'type' => 'string',
'description' => 'Format for phone number url, use %1$s as a placeholder for the value. eg. "tel:%1$s" for regular phone applications or "callto:%1$s" for Skype. Default is "tel:%1$s".',
'default' => 'tel:%1$s',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'phone_number_decoration_class' => array(
'type' => 'string',
'description' => 'CSS class(es) to use as decoration for the HTML rendering of the attribute. eg. "fas fa-phone" will put a phone icon.',
'default' => 'fas fa-phone',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_kpi_duration' => array(
'type' => 'integer',
'description' => 'Level of logging for troubleshooting performance issues (1 to enable, 2 +blame callers) new: add "log_kpi_slow_queries" to limit the stats',
@@ -922,15 +940,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'portal_tickets' => array(
'type' => 'string',
'description' => 'CSV list of classes supported in the portal',
// examples... not used
'default' => 'UserRequest',
'value' => 'UserRequest',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'portal_dispatch_urls' => array(
'type' => 'array',
'description' => 'Associative array of sPortalId => Home page URL (relatively to the application root)',
@@ -1232,8 +1241,18 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'backoffice_default_theme' => array(
'type' => 'string',
'description' => 'Default theme used for '.ITOP_APPLICATION_SHORT.'\'s console',
'default' => 'light-grey',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)
{
return (array_key_exists($sPropCode, $this->m_aSettings));
@@ -1262,12 +1281,16 @@ class Config
* @param string $sPropCode
* @param mixed $value
* @param string $sSourceDesc mandatory for variables with show_in_conf_sample=false
* @param bool $bCanOverride whether the written to file value can still be the non evaluated version on must be the literal
*
* @throws \CoreException
*/
public function Set($sPropCode, $value, $sSourceDesc = 'unknown')
public function Set($sPropCode, $value, $sSourceDesc = 'unknown', $bCanOverride = false)
{
$sType = $this->m_aSettings[$sPropCode]['type'];
$value = $this->oConfigPlaceholdersResolver->Resolve($value);
switch ($sType)
{
case 'bool':
@@ -1287,19 +1310,27 @@ class Config
default:
throw new CoreException('Unknown type for setting', array('property' => $sPropCode, 'type' => $sType));
}
if ($this->m_aSettings[$sPropCode]['value'] == $value)
{
//when you set the exact same value than the previous one, then, you still can preserve the non evaluated version and so on preserve vars/jokers.
$bCanOverride = true;
}
$this->m_aSettings[$sPropCode]['value'] = $value;
$this->m_aSettings[$sPropCode]['source_of_value'] = $sSourceDesc;
$this->m_aCanOverrideSettings[$sPropCode] = $bCanOverride;
}
/**
* @param string $sPropCode
*
* @return mixed
*/
public function Get($sPropCode)
{
return $this->m_aSettings[$sPropCode]['value'];
}
/**
* @param string $sPropCode
*
* @return mixed
*/
public function Get($sPropCode)
{
return $this->m_aSettings[$sPropCode]['value'];
}
/**
* Event log options (see LOG_... definition)
@@ -1371,17 +1402,19 @@ class Config
*/
protected $m_aCharsets;
/**
* Config constructor.
*
* @param string|null $sConfigFile
* @param bool $bLoadConfig
*
* @throws \ConfigException
* @throws \CoreException
*/
public function __construct($sConfigFile = null, $bLoadConfig = true)
/**
* Config constructor.
*
* @param string|null $sConfigFile
* @param bool $bLoadConfig
*
* @throws \ConfigException
* @throws \CoreException
*/
public function __construct($sConfigFile = null, $bLoadConfig = true)
{
$this->oConfigPlaceholdersResolver = new ConfigPlaceholdersResolver();
$this->m_sFile = $sConfigFile;
if (is_null($sConfigFile))
{
@@ -1416,7 +1449,7 @@ class Config
//define default encryption params according to php install
$aEncryptParams = SimpleCrypt::GetNewDefaultParams();
$this->m_sEncryptionLibrary = isset($aEncryptParams['lib']) ? $aEncryptParams['lib'] : DEFAULT_ENCRYPTION_LIB;
$this->m_sEncryptionKey= isset($aEncryptParams['key']) ? $aEncryptParams['key'] : DEFAULT_ENCRYPTION_KEY;
$this->m_sEncryptionKey = isset($aEncryptParams['key']) ? $aEncryptParams['key'] : DEFAULT_ENCRYPTION_KEY;
$this->m_aModuleSettings = array();
@@ -1442,13 +1475,13 @@ class Config
*/
}
/**
* @param string $sPurpose
* @param string $sFileName
*
* @throws \ConfigException
*/
protected function CheckFile($sPurpose, $sFileName)
/**
* @param string $sPurpose
* @param string $sFileName
*
* @throws \ConfigException
*/
protected function CheckFile($sPurpose, $sFileName)
{
if (!file_exists($sFileName))
{
@@ -1491,7 +1524,7 @@ class Config
$sNoise = trim(ob_get_contents());
ob_end_clean();
}
catch(Error $e)
catch (Error $e)
{
// PHP 7
throw new ConfigException('Error in configuration file',
@@ -1542,10 +1575,15 @@ class Config
{
$value = $rawvalue;
}
$this->Set($sPropCode, $value, $sConfigFile);
$this->Set($sPropCode, $value, $sConfigFile, true);
}
}
if (file_exists(READONLY_MODE_FILE))
{
$this->Set('access_mode', ACCESS_READONLY, READONLY_MODE_FILE);
}
$this->m_bLogGlobal = isset($MySettings['log_global']) ? (bool)trim($MySettings['log_global']) : DEFAULT_LOG_GLOBAL;
$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool)trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool)trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
@@ -1575,12 +1613,14 @@ class Config
}
/**
* @param string $sModule
* @see \MetaModel::GetModuleParameter()
*
* @param string $sProperty
* @param mixed $defaultvalue
*
* @param string $sModule
*
* @return mixed|null if present, value defined in the configuration file, if not module parameter from XML
* @see \MetaModel::GetModuleParameter()
*/
public function GetModuleSetting($sModule, $sProperty, $defaultvalue = null)
{
@@ -1593,16 +1633,18 @@ class Config
return $this->GetModuleParameter($sModule, $sProperty, $defaultvalue);
}
/**
* @param string $sModule
* @param string $sProperty
* @param mixed $defaultvalue
*
* @return mixed|null parameter value defined in the XML
* @see \MetaModel::GetModuleSetting() to get from the configuration file first
* @link https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Axml_reference#modules_parameters
*/
public function GetModuleParameter($sModule, $sProperty, $defaultvalue = null)
/**
* @see \MetaModel::GetModuleSetting() to get from the configuration file first
*
* @param string $sProperty
* @param mixed $defaultvalue
*
* @param string $sModule
*
* @return mixed|null parameter value defined in the XML
* @link https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Axml_reference#modules_parameters
*/
public function GetModuleParameter($sModule, $sProperty, $defaultvalue = null)
{
$ret = $defaultvalue;
if (class_exists('ModulesXMLParameters'))
@@ -1848,22 +1890,46 @@ class Config
return $aSettings;
}
/**
* Write the configuration to a file (php format) that can be reloaded later
* By default write to the same file that was specified when constructing the object
*
* @param string $sFileName string Name of the file to write to (emtpy to write to the same file)
*
* @return boolean True otherwise throws an Exception
/**
* Write the configuration to a file (php format) that can be reloaded later
* By default write to the same file that was specified when constructing the object
*
* @throws \ConfigException
*/
* @param string $sFileName string Name of the file to write to (emtpy to write to the same file)
*
* @return boolean True otherwise throws an Exception
*
* @throws \ConfigException
*/
public function WriteToFile($sFileName = '')
{
if (empty($sFileName))
{
$sFileName = $this->m_sFile;
}
$oHandle = null;
$sConfig = null;
if (is_file($this->m_sFile))
{
$oHandle = fopen($this->m_sFile, 'r');
$index = 0;
while (!flock($oHandle, LOCK_SH))
{
if ($index > 50)
{
throw new ConfigException("Could not read to configuration file", array('file' => $this->m_sFile));
}
usleep(100000);
$index++;
}
$sConfig = file_get_contents($this->m_sFile);
}
$this->oItopConfigParser = new iTopConfigParser($sConfig);
if ($oHandle !==null)
{
flock($oHandle, LOCK_UN);
}
$hFile = @fopen($sFileName, 'w');
if ($hFile !== false)
{
@@ -1937,30 +2003,28 @@ class Config
// Write all values that are either always visible or present in the cloned config file
if ($aSettingInfo['show_in_conf_sample'] || (!empty($aSettingInfo['source_of_value']) && ($aSettingInfo['source_of_value'] != 'unknown')))
{
$sType = $aSettingInfo['type'];
switch ($sType)
{
case 'bool':
$sSeenAs = $aSettingInfo['value'] ? 'true' : 'false';
break;
default:
$sSeenAs = self::PrettyVarExport($aSettingInfo['value'], "\t");
}
fwrite($hFile, "\n");
if (isset($aSettingInfo['description']))
{
fwrite($hFile, "\t// $sPropCode: {$aSettingInfo['description']}\n");
}
if (isset($aSettingInfo['default']))
{
$default = $aSettingInfo['default'];
if ($aSettingInfo['type'] == 'bool')
{
$default = $default ? 'true' : 'false';
}
fwrite($hFile,
"\t//\tdefault: ".self::PrettyVarExport($aSettingInfo['default'], "\t//\t\t", true)."\n");
$sComment = self::PrettyVarExport(null,$aSettingInfo['default'], "\t//\t\t", true);
fwrite($hFile,"\t//\tdefault: {$sComment}\n");
}
if (isset($this->m_aCanOverrideSettings[$sPropCode]) && $this->m_aCanOverrideSettings[$sPropCode])
{
$aParserValue = $this->oItopConfigParser->GetVarValue('MySettings', $sPropCode);
}
else
{
$aParserValue = null;
}
$sSeenAs = self::PrettyVarExport($aParserValue,$aSettingInfo['value'], "\t");
fwrite($hFile, "\t'$sPropCode' => $sSeenAs,\n");
}
}
@@ -1974,7 +2038,7 @@ class Config
fwrite($hFile, "\t'$sModule' => array (\n");
foreach ($aProperties as $sProperty => $value)
{
$sNiceExport = self::PrettyVarExport($value, "\t\t");
$sNiceExport = self::PrettyVarExport($this->oItopConfigParser->GetVarValue('MyModuleSettings', $sProperty), $value, "\t\t");
fwrite($hFile, "\t\t'$sProperty' => $sNiceExport,\n");
}
fwrite($hFile, "\t),\n");
@@ -1987,16 +2051,28 @@ class Config
fwrite($hFile, " *\n");
fwrite($hFile, " */\n");
fwrite($hFile, "\$MyModules = array(\n");
fwrite($hFile, "\t'addons' => array (\n");
foreach ($this->m_aAddons as $sKey => $sFile)
$aParserValue = $this->oItopConfigParser->GetVarValue('MyModules', 'addons');
if ($aParserValue['found'])
{
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
fwrite($hFile, "\t'addons' => {$aParserValue['value']},\n");
}
else
{
fwrite($hFile, "\t'addons' => array (\n");
foreach ($this->m_aAddons as $sKey => $sFile)
{
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
}
fwrite($hFile, "\t),\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, ");\n");
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
return fclose($hFile);
$bReturn = fclose($hFile);
utils::SetConfig($this);
return $bReturn;
}
else
{
@@ -2004,16 +2080,16 @@ class Config
}
}
/**
* Helper function to initialize a configuration from the page arguments
*
* @param array $aParamValues
* @param string|null $sModulesDir
* @param bool $bPreserveModuleSettings
*
* @throws \Exception
* @throws \CoreException
*/
/**
* Helper function to initialize a configuration from the page arguments
*
* @param array $aParamValues
* @param string|null $sModulesDir
* @param bool $bPreserveModuleSettings
*
* @throws \Exception
* @throws \CoreException
*/
public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false)
{
if (isset($aParamValues['application_path']))
@@ -2048,7 +2124,7 @@ class Config
$this->Set('db_name', $sDBName);
$this->Set('db_subname', $aParamValues['db_prefix']);
$bDbTlsEnabled = (bool) $aParamValues['db_tls_enabled'];
$bDbTlsEnabled = (bool)$aParamValues['db_tls_enabled'];
if ($bDbTlsEnabled)
{
$this->Set('db_tls.enabled', $bDbTlsEnabled, 'UpdateFromParams');
@@ -2059,9 +2135,12 @@ class Config
$this->Set('db_tls.enabled', $bDbTlsEnabled, null);
}
$sDbTlsCa = $bDbTlsEnabled ? $aParamValues['db_tls_ca'] : null;
if (isset($sDbTlsCa) && !empty($sDbTlsCa)) {
if (isset($sDbTlsCa) && !empty($sDbTlsCa))
{
$this->Set('db_tls.ca', $sDbTlsCa, 'UpdateFromParams');
} else {
}
else
{
// empty parameter : we don't want it in the file
$this->Set('db_tls.ca', null, null);
}
@@ -2145,13 +2224,13 @@ class Config
$this->SetAddOns($aAddOns);
}
/**
* Helper: for an array of string, change the prefix when found
*
* @param array $aStrings
* @param string $sSearchPrefix
* @param string $sNewPrefix
*/
/**
* Helper: for an array of string, change the prefix when found
*
* @param array $aStrings
* @param string $sSearchPrefix
* @param string $sNewPrefix
*/
protected static function ChangePrefix(&$aStrings, $sSearchPrefix, $sNewPrefix)
{
foreach ($aStrings as &$sFile)
@@ -2163,13 +2242,13 @@ class Config
}
}
/**
* Obsolete: kept only for backward compatibility of the Toolkit
* Quick and dirty way to clone a config file into another environment
*
* @param string $sSourceEnv
* @param string $sTargetEnv
*/
/**
* Obsolete: kept only for backward compatibility of the Toolkit
* Quick and dirty way to clone a config file into another environment
*
* @param string $sSourceEnv
* @param string $sTargetEnv
*/
public function ChangeModulesPath($sSourceEnv, $sTargetEnv)
{
// Now does nothing since the includes are built into the environment itself
@@ -2178,6 +2257,7 @@ class Config
/**
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
*
* @param array $aParserValue
* @param mixed $value The value to export
* @param string $sIndentation The string to use to indent the text
* @param bool $bForceIndentation Forces the identation (enven if it breaks/changes an eval, for example to ouput a
@@ -2185,8 +2265,13 @@ class Config
*
* @return string The indented export string
*/
protected static function PrettyVarExport($value, $sIndentation, $bForceIndentation = false)
protected static function PrettyVarExport($aParserValue, $value, $sIndentation, $bForceIndentation = false)
{
if (is_array($aParserValue) && $aParserValue['found'])
{
return $aParserValue['value'];
}
$sExport = var_export($value, true);
$sNiceExport = str_replace(array("\r\n", "\n", "\r"), "\n".$sIndentation, trim($sExport));
if (!$bForceIndentation)
@@ -2206,3 +2291,99 @@ class Config
}
}
class ConfigPlaceholdersResolver
{
/**
* @var null|array
*/
private $aEnv;
/**
* @var null|array
*/
private $aServer;
public function __construct($aEnv = null, $aServer = null)
{
$this->aEnv = $aEnv ?: $_ENV;
$this->aServer = $aServer ?: $_SERVER;
}
public function Resolve($rawValue)
{
if (empty($this->aEnv['ITOP_CONFIG_PLACEHOLDERS']) && empty($this->aServer['ITOP_CONFIG_PLACEHOLDERS']))
{
return $rawValue;
}
if (is_array($rawValue))
{
$aResolvedRawValue = array();
foreach ($rawValue as $key => $value)
{
$aResolvedRawValue[$key] = $this->Resolve($value);
}
return $aResolvedRawValue;
}
if (!is_string($rawValue))
{
return $rawValue;
}
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
{
return $rawValue;
}
$sValue = $rawValue;
foreach ($aMatchesCollection as $aMatches)
{
$sWholeMask = $aMatches[0];
$sSource = $aMatches[1];
$sKey = $aMatches[2];
$sDefault = isset($aMatches[3]) ? $aMatches[3] : null;
$sReplacement = $this->Get($sSource, $sKey, $sDefault, $sWholeMask);
$sValue = str_replace($sWholeMask, $sReplacement, $sValue);
}
return $sValue;
}
private function Get($sSourceName, $sKey, $sDefault, $sWholeMask)
{
if ('env' == $sSourceName)
{
$aSource = $this->aEnv;
}
else if ('server' == $sSourceName)
{
$aSource = $this->aServer;
}
else
{
$sErrorMessage = sprintf('unsupported source name "%s" into "%s"', $sSourceName, $sWholeMask);
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
throw new ConfigException($sErrorMessage);
}
if (array_key_exists($sKey, $aSource))
{
return $aSource[$sKey];
}
if (null !== $sDefault)
{
return $sDefault;
}
$sErrorMessage = sprintf('key "%s" not found into "%s" while expanding', $sSourceName, $sWholeMask);
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
throw new ConfigException($sErrorMessage);
}
}

View File

@@ -19,14 +19,14 @@
/**
* Simple helper class for keeping track of the context inside the call stack
*
*
* To check (anywhere in the code) if a particular context tag is present
* in the call stack simply do:
*
*
* if (ContextTag::Check(<the_tag>)) ...
*
*
* For example to know if the code is being executed in the context of a portal do:
*
*
* if (ContextTag::Check('GUI:Portal'))
*
* @copyright Copyright (C) 2016-2017 Combodo SARL
@@ -35,8 +35,15 @@
class ContextTag
{
const TAG_PORTAL = 'GUI:Portal';
const TAG_CRON = 'CRON';
const TAG_CONSOLE = 'GUI:Console';
const TAG_SETUP = 'Setup';
const TAG_SYNCHRO = 'Synchro';
const TAG_REST = 'REST/JSON';
protected static $aStack = array();
/**
* Store a context tag on the stack
* @param string $sTag
@@ -46,6 +53,11 @@ class ContextTag
static::$aStack[] = $sTag;
}
public static function AddContext($sTag)
{
static::$aStack[] = $sTag;
}
/**
* Cleanup the context stack
*/
@@ -53,7 +65,7 @@ class ContextTag
{
array_pop(static::$aStack);
}
/**
* Check if a given tag is present in the stack
* @param string $sTag
@@ -63,13 +75,53 @@ class ContextTag
{
return in_array($sTag, static::$aStack);
}
/**
* Get the whole stack as an array
* @return hash
* @return array
*/
public static function GetStack()
{
return static::$aStack;
}
}
/**
* Get all the predefined context tags
* @return array
*/
public static function GetTags()
{
$aRawTags = array(
ContextTag::TAG_REST,
ContextTag::TAG_SYNCHRO,
ContextTag::TAG_SETUP,
ContextTag::TAG_CONSOLE,
ContextTag::TAG_CRON,
ContextTag::TAG_PORTAL);
$aTags = array();
foreach ($aRawTags as $sRawTag)
{
$aTags[$sRawTag] = Dict::S("Core:Context={$sRawTag}");
}
$aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = array();
foreach ($aPortalsConf as $sPortalId => $aConf)
{
$sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
}
foreach ($aDispatchers as $sPortalId => $oDispatcher)
{
if ($sPortalId != 'backoffice')
{
$aTags['Portal:'.$sPortalId] = $oDispatcher->GetLabel();
}
}
return $aTags;
}
}

View File

@@ -115,7 +115,7 @@ class CoreException extends Exception
* @see \DBObject::DBInsertNoReload()
* @see \DBObject::DBUpdate()
*
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
*/
class CoreCannotSaveObjectException extends CoreException
{
@@ -183,6 +183,30 @@ class CoreCannotSaveObjectException extends CoreException
}
}
/**
* @since 2.7.0 N°2555
*/
class CorePortalInvalidActionRuleException extends CoreException
{
}
/**
* @since 2.7.0 N°2555
*/
class CoreOqlException extends CoreException
{
}
/**
* @since 2.7.0 N°2555
*/
class CoreOqlMultipleResultsForbiddenException extends CoreOqlException
{
}
class CoreWarning extends CoreException
{
}
@@ -213,3 +237,13 @@ class ArchivedObjectException extends CoreException
class InvalidConfigParamException extends CoreException
{
}
/**
* Throwned when the password is not valid
*
* @since 2.7.0
*/
class InvalidPasswordAttributeOneWayPassword extends CoreException
{
}

251
core/counter.class.inc.php Normal file
View File

@@ -0,0 +1,251 @@
<?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
*/
/**
* Class ItopCounter
*
*/
final class ItopCounter
{
/**
* Key based counter.
* The counter is protected against concurrency script.
*
* @param $sCounterName
* @param null|callable $oNewObjectValueProvider optional callable that must return an integer. Used when no key is found
*
* @return int the counter starting at
* * `0` when no $oNewObjectValueProvider is given (or null)
* * `$oNewObjectValueProvider() + 1` otherwise
*
* @throws \CoreException
* @throws \MySQLException
* @throws \Exception
*/
public static function Inc($sCounterName, $oNewObjectValueProvider = null)
{
$sSelfClassName = self::class;
$sMutexKeyName = "{$sSelfClassName}-{$sCounterName}";
$oiTopMutex = new iTopMutex($sMutexKeyName);
$oiTopMutex->Lock();
$bIsInsideTransaction = CMDBSource::IsInsideTransaction();
if ($bIsInsideTransaction)
{
// # Transaction isolation hack:
// When inside a transaction, we need to open a new connection for the counter.
// So it is visible immediately to the connections outside of the transaction.
// Either way, the lock is not long enought, and there would be duplicate ref.
//
// SELECT ... FOR UPDATE would have also worked but with the cost of extra long lock (until the commit),
// we did not wanted this! As opening a short connection is less prone to starving than a long running one.
// Plus it would trigger way more deadlocks!
$hDBLink = self::InitMySQLSession();
}
else
{
$hDBLink = CMDBSource::GetMysqli();
}
try
{
$oFilter = DBObjectSearch::FromOQL('SELECT KeyValueStore WHERE key_name=:key_name AND namespace=:namespace', array(
'key_name' => $sCounterName,
'namespace' => $sSelfClassName,
));
$oAttDef = MetaModel::GetAttributeDef('KeyValueStore', 'value');
$aAttToLoad = array('KeyValueStore' => array('value' => $oAttDef));
$sSql = $oFilter->MakeSelectQuery(array(), array(), $aAttToLoad);
$hResult = mysqli_query($hDBLink, $sSql);
$aCounter = mysqli_fetch_array($hResult, MYSQLI_NUM);
mysqli_free_result($hResult);
//Rebuild the filter, as the MakeSelectQuery polluted the orignal and it cannot be reused
$oFilter = DBObjectSearch::FromOQL('SELECT KeyValueStore WHERE key_name=:key_name AND namespace=:namespace', array(
'key_name' => $sCounterName,
'namespace' => $sSelfClassName,
));
if (is_null($aCounter))
{
if (null != $oNewObjectValueProvider)
{
$iComputedValue = $oNewObjectValueProvider();
}
else
{
$iComputedValue = 0;
}
$iCurrentValue = $iComputedValue + 1;
$aQueryParams = array(
'key_name' => $sCounterName,
'value' => "$iCurrentValue",
'namespace' => $sSelfClassName,
);
$sSql = $oFilter->MakeInsertQuery($aQueryParams);
}
else
{
$iCurrentValue = (int) $aCounter[1];
$iCurrentValue++;
$aQueryParams = array(
'value' => "$iCurrentValue",
);
$sSql = $oFilter->MakeUpdateQuery($aQueryParams);
}
$hResult = mysqli_query($hDBLink, $sSql);
}
catch(Exception $e)
{
IssueLog::Error($e->getMessage());
throw $e;
}
finally
{
if ($bIsInsideTransaction)
{
mysqli_close($hDBLink);
}
$oiTopMutex->Unlock();
}
return $iCurrentValue;
}
/**
* handle a counter for the root class of given $sLeafClass.
* If no counter exist initialize it with the `max(id) + 1`
*
*
*
* @param $sLeafClass
*
* @return int
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreOqlMultipleResultsForbiddenException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
public static function IncClass($sLeafClass)
{
$sRootClass = MetaModel::GetRootClass($sLeafClass);
$oNewObjectCallback = function() use ($sRootClass)
{
$sRootTable = MetaModel::DBGetTable($sRootClass);
$sIdField = MetaModel::DBGetKey($sRootClass);
return CMDBSource::QueryToScalar("SELECT max(`$sIdField`) FROM `$sRootTable`");
};
return self::Inc($sRootClass, $oNewObjectCallback);
}
/**
* @return \mysqli
* @throws \ConfigException
* @throws \CoreException
* @throws \MySQLException
*/
private static function InitMySQLSession()
{
$oConfig = utils::GetConfig();
$sDBHost = $oConfig->Get('db_host');
$sDBUser = $oConfig->Get('db_user');
$sDBPwd = $oConfig->Get('db_pwd');
$sDBName = $oConfig->Get('db_name');
$bDBTlsEnabled = $oConfig->Get('db_tls.enabled');
$sDBTlsCA = $oConfig->Get('db_tls.ca');
$hDBLink = CMDBSource::GetMysqliInstance($sDBHost, $sDBUser, $sDBPwd, $sDBName, $bDBTlsEnabled, $sDBTlsCA, false);
if (!$hDBLink)
{
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
}
return $hDBLink;
}
}
/**
* Persistent classes for a CMDB
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class KeyValueStore extends DBObject
{
public static function Init()
{
$aParams = array(
'category' => '',
'key_type' => 'autoincrement',
'name_attcode' => array('key_name'),
'state_attcode' => '',
'reconc_keys' => array(''),
'db_table' => 'key_value_store',
'db_key_field' => 'id',
'db_finalclass_field' => '',
'indexes' => array (
array (
0 => 'key_name',
1 => 'namespace',
),
),);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("namespace", array("allowed_values"=>null, "sql"=>'namespace', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("key_name", array("allowed_values"=>null, "sql"=>'key_name', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>'value', "default_value"=>'0', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_SetZListItems('details', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
MetaModel::Init_SetZListItems('standard_search', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
MetaModel::Init_SetZListItems('list', array (
0 => 'key_name',
1 => 'value',
2 => 'namespace',
));
;
}
}

View File

@@ -83,8 +83,9 @@ abstract class DBObject implements iDisplay
/** @var bool true IF the object is mapped to a DB record */
protected $m_bIsInDB = false;
protected $m_iKey = null;
/** @var array key: attcode, value: corresponding current value (in memory, before persisting object) */
/** @var array attcode => value : corresponding current value (the new value passed to {@see DBObject::Set()}). Reset during {@see DBObject::DBUpdate()} */
private $m_aCurrValues = array();
/** @var array attcode => value : previous values before the {@see DBObject::Set()} call. Array is reset at the end of {@see DBObject::DBUpdate()} */
protected $m_aOrigValues = array();
protected $m_aExtendedData = null;
@@ -97,7 +98,8 @@ abstract class DBObject implements iDisplay
private $m_bDirty = false;
/**
* @var boolean|null true if the object has been verified and is consistent with integrity rules. If null, then the check has to be performed again to know the status
* @var boolean|null true if the object has been verified and is consistent with integrity rules.
* If null, then the check has to be performed again to know the status
* @see CheckToWrite()
*/
private $m_bCheckStatus = null;
@@ -114,7 +116,7 @@ abstract class DBObject implements iDisplay
/**
* @var null|string[] list of warnings thrown during DB write
* @see CheckToWrite()
* @since 2.6 N°659 uniqueness constraints
* @since 2.6.0 N°659 uniqueness constraints
*/
protected $m_aCheckWarnings = null;
protected $m_aDeleteIssues = null;
@@ -134,10 +136,11 @@ abstract class DBObject implements iDisplay
*/
protected $m_aModifiedAtt = array();
/**
* @var array attname => currentvalue Persists changes for {@link DBUpdate}
* @var array attname => value : value before the last {@see DBObject::Set()} call. Set at the beginning of {@see DBObject::DBUpdate()}.
* @see DBObject::ListPreviousValuesForUpdatedAttributes() getter for this attribute
* @since 2.7.0 N°2293
*/
protected $m_aChanges;
protected $m_aPreviousValuesForUpdatedAttributes;
/**
* @var array Set of Synch data related to this object
* <ul>
@@ -642,7 +645,7 @@ abstract class DBObject implements iDisplay
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
* @since 2.6
* @since 2.6.0
*/
public function SetIfNull($sAttCode, $value)
{
@@ -698,21 +701,22 @@ abstract class DBObject implements iDisplay
return $oAttDef->GetLabel();
}
/**
* Getter : get a value from the current object of from a related object
*
* Get the value of the attribute $sAttCode
* This call may involve an object reload if the object was not completely loaded (lazy loading)
*
* @api
*
* @param string $sAttCode Could be an extended attribute code in the form extkey_id->anotherkey_id->remote_attr
*
* @return mixed|string
*
* @throws ArchivedObjectException
* @throws CoreException
*/
/**
* Getter : get a value from the current object of from a related object
*
* Get the value of the attribute $sAttCode
* This call may involve an object reload if the object was not completely loaded (lazy loading)
*
* @api
*
* @param string $sAttCode Could be an extended attribute code in the form extkey_id->anotherkey_id->remote_attr
*
* @return mixed|string
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \Exception
*/
public function Get($sAttCode)
{
if (($iPos = strpos($sAttCode, '->')) === false)
@@ -852,17 +856,13 @@ abstract class DBObject implements iDisplay
}
/**
* Get the value as it was before change with Set
*
* The original value vary according to the persisted state
* - not persisted: NULL
* - persisted: the "in DB" value
*
* @param string $sAttCode
*
* @return mixed|null the original value
* @return mixed|null the value as it was before changed with {@see DBObject::Set()}.
* Returns null if the attribute wasn't changed.
*
* @throws CoreException
* @see DBObject::$m_aOrigValues
* @throws CoreException if the attribute is unknown for the current object
*/
public function GetOriginal($sAttCode)
{
@@ -1072,21 +1072,20 @@ abstract class DBObject implements iDisplay
}
/**
* Get $sAttCode formatted as HTML
*
* The returned string is already escaped, and as such is protected against XSS
* The markup relies on a few assumptions (CSS) that could change without notice
*
* @api
*
* @param string $sAttCode
* @param bool $bLocalize
*
* @return string
* @return string $sAttCode formatted as HTML for the console details forms (when viewing, not when editing !)
* The returned string is already escaped, and as such is protected against XSS
* The markup relies on a few assumptions (CSS) that could change without notice
*
* @throws ArchivedObjectException
* @throws CoreException
* @throws DictExceptionMissingString
*
* @see \Combodo\iTop\Form\Field\Field for rendering in portal forms
*/
public function GetAsHTML($sAttCode, $bLocalize = true)
{
@@ -1671,6 +1670,7 @@ abstract class DBObject implements iDisplay
*
* @return integer the binary combination of flags for the given attribute in the given state of the object.
* Values can be one of the OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY, ... (see define in metamodel.class.php)
* Combine multiple values using the "|" operator, for example `OPT_ATT_READONLY | OPT_ATT_HIDDEN`.
*
* @throws \CoreException
*
@@ -1912,7 +1912,7 @@ abstract class DBObject implements iDisplay
return "Bad type";
}
elseif ($oAtt instanceof AttributeClassAttCodeSet)
elseif (($oAtt instanceof AttributeClassAttCodeSet) || ($oAtt instanceof AttributeEnumSet))
{
if (is_string($toCheck))
{
@@ -1988,7 +1988,7 @@ abstract class DBObject implements iDisplay
* @throws \CoreException
* @throws \OQLException
*
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
* @api
*/
protected function DoCheckUniqueness()
@@ -2003,6 +2003,8 @@ abstract class DBObject implements iDisplay
continue;
}
// No iTopMutex so there might be concurrent access !
// But the necessary lock would have a high performance cost :(
$bHasDuplicates = $this->HasObjectsInDbForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties);
if ($bHasDuplicates)
{
@@ -2034,7 +2036,7 @@ abstract class DBObject implements iDisplay
* @return string dict key : Class:$sClassName/UniquenessRule:$sUniquenessRuleId if none then will use Core:UniquenessDefaultError
* Dictionary keys can contain "$this" placeholders
*
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
*/
protected function GetUniquenessRuleMessage($sUniquenessRuleId)
{
@@ -2086,7 +2088,7 @@ abstract class DBObject implements iDisplay
* @return \DBSearch
* @throws \CoreException
* @throws \OQLException
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
* @api
*/
protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties)
@@ -2235,7 +2237,7 @@ abstract class DBObject implements iDisplay
/**
* Check if it is allowed to delete the existing object from the database
*
* an array of displayable error is added in {@link $m_aDeleteIssues}
* an array of displayable error is added in {@see DBObject::$m_aDeleteIssues}
*
* @internal
*
@@ -2374,12 +2376,12 @@ abstract class DBObject implements iDisplay
}
/**
* List the attributes that have been changed since the object has been loaded from the DB
*
* @api
* @api-advanced
*
* @return array attname => currentvalue
* @return array attname => currentvalue List the attributes that have been changed using {@see DBObject::Set()}. Reset during {@see DBObject::DBUpdate()}
* @uses m_aCurrValues
* @see \DBObject::ListPreviousValuesForUpdatedAttributes()
* @throws Exception
*/
public function ListChanges()
@@ -2388,12 +2390,35 @@ abstract class DBObject implements iDisplay
{
return $this->ListChangedValues($this->m_aCurrValues);
}
else
{
return $this->m_aCurrValues;
}
return $this->m_aCurrValues;
}
/**
* @api
* @api-advanced
*
* To be used during the {@link \DBObject::DBUpdate()} call stack.
*
* To get values that were set to the changed fields, simply use {@link \DBObject::Get()}
*
* @return array attname => value : value that was present before the last {@see DBObject::Set()} call.
* This array is set at the beginning of {@see DBObject::DBpdate()} using {@see DBObject::InitPreviousValuesForUpdatedAttributes()}.
* @uses m_aPreviousValuesForUpdatedAttributes
* @see \DBObject::ListChanges()
* @since 2.7.0 N°2293
*/
public function ListPreviousValuesForUpdatedAttributes()
{
if (empty($this->m_aPreviousValuesForUpdatedAttributes))
{
return array();
}
return $this->m_aPreviousValuesForUpdatedAttributes;
}
/**
* Whether or not an object was modified since last read from the DB
* (ie: does it differ from the DB ?)
@@ -2640,7 +2665,7 @@ abstract class DBObject implements iDisplay
*
* @return int key of the newly created object
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException if {@link CheckToWrite()} returns issues
* @throws \CoreCannotSaveObjectException if {@see DBObject::CheckToWrite()} returns issues
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
@@ -2735,6 +2760,11 @@ abstract class DBObject implements iDisplay
$this->DBInsertSingleTable($sParentClass);
}
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
if ($bIsTransactionEnabled)
{
CMDBSource::Query('COMMIT');
@@ -2749,11 +2779,6 @@ abstract class DBObject implements iDisplay
throw $e;
}
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->m_bIsInDB = true;
$this->m_bDirty = false;
foreach ($this->m_aCurrValues as $sAttCode => $value)
@@ -2925,16 +2950,13 @@ abstract class DBObject implements iDisplay
/**
* @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 ArchivedObjectException
* @throws CoreCannotSaveObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws CoreWarning
* @throws MySQLException
* @throws OQLException
*/
public function DBInsertTracked(CMDBChange $oChange)
{
@@ -2945,6 +2967,9 @@ abstract class DBObject implements iDisplay
/**
* @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
@@ -3005,13 +3030,14 @@ abstract class DBObject implements iDisplay
/**
* Update an object in DB
*
* @api
* @see DBWrite
*
* @api
* @see DBObject::DBWrite()
*
* @return int object key
*
*
* @throws \CoreException
* @throws \CoreCannotSaveObjectException if CheckToWrite() returns issues
* @throws \Exception
*/
public function DBUpdate()
{
@@ -3028,7 +3054,8 @@ abstract class DBObject implements iDisplay
}
$aUpdateReentrance[$sKey] = true;
$this->m_aChanges = array(); // reset attribute to avoid stack collisions
$this->InitPreviousValuesForUpdatedAttributes();
try
{
$this->DoComputeValues();
@@ -3181,7 +3208,9 @@ abstract class DBObject implements iDisplay
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->m_aChanges = $this->ListChanges(); // N°2293 save changes for use in user callbacks
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
// new values are already in the object (call {@see DBObject::Get()} to get them)
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
$this->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
@@ -3285,10 +3314,37 @@ abstract class DBObject implements iDisplay
return $this->m_iKey;
}
/**
* @internal
* Save updated fields previous values for {@see DBObject::DBUpdate()} callbacks
* @see DBObject::ListPreviousValuesForUpdatedAttributes() to get the data in the callbacks
* @uses ListChanges
* @uses m_aOrigValues
* @uses m_aPreviousValuesForUpdatedAttributes
* @since 2.7.0 N°2293
* @throws \Exception
*/
private function InitPreviousValuesForUpdatedAttributes()
{
$aChanges= $this->ListChanges();
if (empty($aChanges))
{
$this->m_aPreviousValuesForUpdatedAttributes = array();
return;
}
$aPreviousValuesForUpdatedAttributes = array_intersect_key($this->m_aOrigValues, $aChanges);
$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
@@ -3558,9 +3614,12 @@ abstract class DBObject implements iDisplay
/**
* @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 null $bSkipStrongSecurity
* @param null $oDeletionPlan
* @param boolean $bSkipStrongSecurity
* @param \DeletionPlan $oDeletionPlan
*
* @throws ArchivedObjectException
* @throws CoreCannotSaveObjectException
@@ -3648,7 +3707,8 @@ abstract class DBObject implements iDisplay
if (!array_key_exists($sStimulusCode, $aStateTransitions))
{
// This simulus has no effect in the current state... do nothing
return true;
IssueLog::Error(get_class($this).": Transition $sStimulusCode is not allowed in ".$this->Get($sStateAttCode));
return false;
}
$aTransitionDef = $aStateTransitions[$sStimulusCode];
@@ -4108,11 +4168,15 @@ abstract class DBObject implements iDisplay
}
/**
* This method is called after the object is updated into DB. You can get changes by calling {@link ListChanges}.
*
* @overwritable-hook You can extend this method in order to provide your own logic.
*
* @since 2.7.0 N°2293 can access object changes by calling {@link ListChanges}
* This method is called after the object is updated into DB, and just before the {@see DBObject::Reload()} call.
*
* Warning : do not use {@see DBObject::ListChanges()} as it will return an empty array !
* Use instead {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get modified fields and their previous values,
* and {@see DBObject::Get()} to get the persisted value for a given attribute.
*
* @since 2.7.0 N°2293 can access object changes by calling {@see DBObject::ListPreviousValuesForUpdatedAttributes()}
*/
protected function AfterUpdate()
{

View File

@@ -337,7 +337,7 @@ class DBObjectSearch extends DBSearch
$this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams);
}
protected function RenameParam($sOldName, $sNewName)
public function RenameParam($sOldName, $sNewName)
{
$this->m_oSearchCondition->RenameParam($sOldName, $sNewName);
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
@@ -528,7 +528,7 @@ class DBObjectSearch extends DBSearch
*
* @throws \CoreException
*
* @since 2.5 N°1418
* @since 2.5.0 N°1418
*/
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
{
@@ -622,7 +622,7 @@ class DBObjectSearch extends DBSearch
{
if (!$oAttDef->IsScalar()) continue;
if ($oAttDef->IsExternalKey()) continue;
if ($oAttDef instanceof AttributePassword) continue;
if (!$oAttDef->IsSearchable()) continue;
$aFullTextFields[] = new FieldExpression($sAttCode, $this->GetClassAlias());
}
$oTextFields = new CharConcatWSExpression(' ', $aFullTextFields);
@@ -699,6 +699,37 @@ class DBObjectSearch extends DBSearch
}
}
/**
* Rename aliases of nested queries to avoid duplicates with main query
*
* @param array $aClassAliases array of alias => class for the main query
* @param array $aAliasTranslation translation table of main query to apply to nested queries
*/
public function RenameNestedQueriesAliasesInNameSpace($aClassAliases, $aAliasTranslation = array())
{
$this->GetCriteria()->Browse(function ($oNode) use ($aClassAliases, $aAliasTranslation) {
if ($oNode instanceof NestedQueryExpression)
{
$oNestedQuery = $oNode->GetNestedQuery();
$oNestedQuery->RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation);
}
});
}
public function RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation = array())
{
// Recurse in nested queries
$this->RenameNestedQueriesAliasesInNameSpace($aClassAliases, $aAliasTranslation);
$this->AddToNameSpace($aClassAliases, $aAliasTranslation);
$this->TranslateConditions($aAliasTranslation, false, false);
}
public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
$oExpression = $this->GetCriteria()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
$this->ResetCondition();
$this->AddConditionExpression($oExpression);
}
// Browse the tree nodes recursively
//
@@ -873,6 +904,15 @@ class DBObjectSearch extends DBSearch
return $res;
}
/**
* @param \DBObjectSearch $oFilter
* @param string $sExtKeyAttCode
* @param array $aClassAliases
* @param array $aAliasTranslation
* @param int $iOperatorCode
*
* @throws \CoreException
*/
protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
{
// Find the node on which the new tree must be attached (most of the time it is "this")
@@ -883,9 +923,10 @@ class DBObjectSearch extends DBSearch
{
foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting)
{
/** @var DBObjectSearch $oExisting */
if ($oExisting->GetClass() == $oFilter->GetClass())
{
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
$oExisting->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
$bMerged = true;
break;
}
@@ -953,7 +994,7 @@ class DBObjectSearch extends DBSearch
$this->RecomputeClassList($this->m_aClasses);
}
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
{
$sForeignClass = $oFilter->GetClass();
@@ -967,7 +1008,7 @@ class DBObjectSearch extends DBSearch
{
if ($oExisting->GetClass() == $oFilter->GetClass())
{
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
$oExisting->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
$bMerged = true;
break;
}
@@ -980,10 +1021,115 @@ class DBObjectSearch extends DBSearch
}
}
/**
* Filter this search with another search.
* Initial search is unmodified.
* The difference with Intersect, is that an alias can be provided,
* the filtered class does not need to be the first joined class.
*
* @param string $sClassAlias class being filtered
* @param DBSearch $oFilter Filter to apply
*
* @return DBSearch The filtered search
* @throws \CoreException
*/
public function Filter($sClassAlias, DBSearch $oFilter)
{
// If the conditions are the correct ones for Intersect
if (($this->GetFirstJoinedClass() == $oFilter->GetFirstJoinedClass()))
{
return $this->Intersect($oFilter);
}
/** @var \DBObjectSearch $oFilteredSearch */
$oFilteredSearch = $this->DeepClone();
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses);
if ($oFilterExpression === false)
{
throw new CoreException("Limitation: cannot filter search");
}
$oFilteredSearch->AddConditionExpression($oFilterExpression);
return $oFilteredSearch;
}
/**
* Filter "in place" the search (filtered part is replaced in the initial search)
*
* @param DBObjectSearch $oSearch Search to filter, modified with the given filter
* @param string $sClassAlias class to filter
* @param \DBSearch $oFilter Filter to apply
*
* @return \Expression|false
* @throws \CoreException
*/
private static function FilterSubClass(DBObjectSearch &$oSearch, $sClassAlias, DBSearch $oFilter, $aRootClasses)
{
if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias))
{
$oSearch->ResetCondition();
$oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses);
return $oSearch->GetCriteria();
}
/** @var Expression $oFilterExpression */
// Search in the filter tree where is the correct DBSearch
foreach ($oSearch->m_aPointingTo as $sExtKey => $aPointingTo)
{
foreach ($aPointingTo as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $index => $oExtFilter)
{
$oFilterExpression = self::FilterSubClass($oExtFilter, $sClassAlias, $oFilter, $aRootClasses);
if ($oFilterExpression !== false)
{
$oSearch->m_aPointingTo[$sExtKey][$iOperatorCode][$index] = $oExtFilter;
return $oFilterExpression;
}
}
}
}
foreach($oSearch->m_aReferencedBy as $sForeignClass => $aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $index => $oForeignFilter)
{
$oFilterExpression = self::FilterSubClass($oForeignFilter, $sClassAlias, $oFilter, $aRootClasses);
if ($oFilterExpression !== false)
{
$oSearch->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][$index] = $oForeignFilter;
return $oFilterExpression;
}
}
}
}
}
return false;
}
/**
* @inheritDoc
* @throws \CoreException
*/
public function Intersect(DBSearch $oFilter)
{
return $this->IntersectSubClass($oFilter, $this->m_aClasses);
}
/**
* @param \DBSearch $oFilter
* @param array $aRootClasses classes of the root search (for aliases)
*
* @return \DBUnionSearch|mixed
* @throws \CoreException
*/
protected function IntersectSubClass(DBSearch $oFilter, $aRootClasses)
{
if ($oFilter instanceof DBUnionSearch)
{
@@ -999,15 +1145,12 @@ class DBObjectSearch extends DBSearch
foreach ($aFilters as $oRightFilter)
{
// Limitation: the queried class must be the first declared class
if ($this->GetFirstJoinedClassAlias() != $this->GetClassAlias())
{
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$this->GetClass()} AS {$this->GetClassAlias()}) is not the first joined class ({$this->GetFirstJoinedClass()} AS {$this->GetFirstJoinedClassAlias()})");
}
if ($oRightFilter->GetFirstJoinedClassAlias() != $oRightFilter->GetClassAlias())
{
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$oRightFilter->GetClass()} AS {$oRightFilter->GetClassAlias()}) is not the first joined class ({$oRightFilter->GetFirstJoinedClass()} AS {$oRightFilter->GetFirstJoinedClassAlias()})");
}
/** @var \DBObjectSearch $oLeftFilter */
$oLeftFilter = $this->DeepClone();
$oRightFilter = $oRightFilter->DeepClone();
@@ -1017,14 +1160,14 @@ class DBObjectSearch extends DBSearch
$oLeftFilter->AllowAllData();
}
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass())
{
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass()))
{
// Specialize $oLeftFilter
$oLeftFilter->ChangeClass($oRightFilter->GetClass());
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
}
elseif (MetaModel::IsParentClass($oRightFilter->GetClass(), $oLeftFilter->GetClass()))
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
{
// Specialize $oRightFilter
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
@@ -1036,7 +1179,9 @@ class DBObjectSearch extends DBSearch
}
$aAliasTranslation = array();
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $oLeftFilter->m_aClasses, $aAliasTranslation);
$oLeftFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
$oRightFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
$aSearches[] = $oLeftFilter;
}
@@ -1051,15 +1196,22 @@ class DBObjectSearch extends DBSearch
}
}
/**
* @param DBObjectSearch $oFilter
* @param array $aClassAliases
* @param array $aAliasTranslation
*
* @throws CoreException
*/
protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation)
{
if ($this->GetClass() != $oFilter->GetClass())
if ($this->GetFirstJoinedClass() != $oFilter->GetClass())
{
throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'");
throw new CoreException("Attempting to merge a filter of class '{$this->GetFirstJoinedClass()}' with a filter of class '{$oFilter->GetClass()}'");
}
// Translate search condition into our aliasing scheme
$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias();
$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetFirstJoinedClassAlias();
foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
@@ -1399,12 +1551,97 @@ class DBObjectSearch extends DBSearch
{
return new IntervalExpression($oExpression->GetValue(), $oExpression->GetUnit());
}
elseif ($oExpression instanceof NestedQueryOqlExpression)
{
return NestedQueryExpression::FromOQLObjectQuery($oExpression->GetOQLObjectQuery());
}
else
{
throw new CoreException('Unknown expression type', array('class'=>get_class($oExpression), 'query'=>$sQuery));
}
}
/**
* {@inheritDoc}
* @see DBSearch::ToJSON()
*/
public function ToJSON()
{
$aRet = array('selects' => array(), 'joins' => array(), 'where' => array());
$aParams = array_merge($this->m_aParams);
$aParams = MetaModel::PrepareQueryArguments($aParams);
foreach ($this->m_aSelectedClasses as $sAlias => $sClass)
{
$aRet['selects'][] = array('class' => $sClass, 'alias' => $sAlias);
}
$this->JoinsToJSON($aRet);
$aRet['condition'] = $this->m_oSearchCondition->ToJSON($aParams, true);
return $aRet;
}
/**
* Export the JOIN operations to a structure (array of arrays) suitable for JSON export
*
* @internal
*
* @param mixed[string] $aRet
* @return void
*/
protected function JoinsToJSON(&$aRet)
{
foreach($this->m_aPointingTo as $sExtKey => $aPointingTo)
{
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
$sOperator = $this->OperatorCodeToOQL($iOperatorCode);
foreach($aFilter as $oFilter)
{
$aRet['joins'][] = array(
'src' => $this->GetFirstJoinedClass(),
'src_alias' => $this->GetFirstJoinedClassAlias(),
'target' => $oFilter->GetFirstJoinedClass(),
'target_alias' => $oFilter->GetFirstJoinedClassAlias(),
'foreign_key' => $sExtKey,
'operator' => $sOperator,
);
$oFilter->JoinsToJSON($aRet);
}
}
}
foreach($this->m_aReferencedBy as $aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
{
$sOperator = $this->OperatorCodeToOQL($iOperatorCode);
foreach ($aFilters as $oForeignFilter)
{
$aRet['joins'][] = array(
'src' => $oForeignFilter->GetFirstJoinedClass(),
'src_alias' => $oForeignFilter->GetFirstJoinedClassAlias(),
'target' => $this->GetFirstJoinedClass(),
'target_alias' => $this->GetFirstJoinedClassAlias(),
'foreign_key' => $sForeignExtKeyAttCode,
'operator' => $sOperator,
);
$oForeignFilter->JoinsToJSON($aRet);
}
}
}
}
}
/**
* @param \OqlQuery $oOqlQuery
* @param string $sQuery
*
* @throws \CoreException
* @throws \Exception
*/
public function InitFromOqlQuery(OqlQuery $oOqlQuery, $sQuery)
{
$oModelReflection = new ModelReflectionRuntime();
@@ -1521,6 +1758,10 @@ class DBObjectSearch extends DBSearch
foreach ($oOqlQuery->GetSelectedClasses() as $oClassDetails)
{
$sClassToSelect = $oClassDetails->GetValue();
if (!array_key_exists($sClassToSelect, $aAliases))
{
throw new CoreException("$sClassToSelect is not a valid alias");
}
$this->m_aSelectedClasses[$sClassToSelect] = $aAliases[$sClassToSelect];
}
$this->m_aClasses = $aAliases;
@@ -1557,6 +1798,24 @@ class DBObjectSearch extends DBSearch
return $sRet;
}
/**
* Generate an INSERT statement.
* Note : unlike `RenderUpdate` and `RenderSelect`, it is limited to one and only one table.
*
* @param array $aValues is an array of $sAttCode => $value
* @param array $aArgs
*
* @return string
* @throws \CoreException
*/
public function MakeInsertQuery($aValues, $aArgs = array())
{
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this);
$oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectUpdateQuery($aValues);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
$sRet = $oSQLQuery->RenderInsert($aScalarArgs);
return $sRet;
}
/**
* Get an SQLObjectQuery from the search. This SQLObjectQuery can be rendered as a select, select group by, update or delete

View File

@@ -89,7 +89,8 @@ class DBObjectSet implements iDBObjectSetIterator
* @api
*
* @param DBSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending (true for ASC, false, for DESC)
* Example : array('name' => true, 'id' => false)
* @param array $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param array $aExtendedDataSpec
* @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count)
@@ -217,73 +218,81 @@ class DBObjectSet implements iDBObjectSetIterator
*/
public function OptimizeColumnLoad($aAttToLoad)
{
if (is_null($aAttToLoad))
// Check that the structure is an array of array
if (!is_array($aAttToLoad))
{
$this->m_aAttToLoad = null;
return;
}
else
foreach ($aAttToLoad as $sAlias => $aAttCodes)
{
// Complete the attribute list with the attribute codes
$aAttToLoadWithAttDef = array();
foreach($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
if (!is_array($aAttCodes))
{
$aAttToLoadWithAttDef[$sClassAlias] = array();
if (array_key_exists($sClassAlias, $aAttToLoad))
$this->m_aAttToLoad = null;
return;
}
}
// Complete the attribute list with the attribute codes
$aAttToLoadWithAttDef = array();
foreach($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
{
$aAttToLoadWithAttDef[$sClassAlias] = array();
if (array_key_exists($sClassAlias, $aAttToLoad))
{
$aAttList = $aAttToLoad[$sClassAlias];
foreach($aAttList as $sAttToLoad)
{
$aAttList = $aAttToLoad[$sClassAlias];
foreach($aAttList as $sAttToLoad)
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
// Add the external key friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
if (MetaModel::IsArchivable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the external key friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_archive_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_archive_flag'] = $oArchiveFlagAttDef;
}
if (MetaModel::IsArchivable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_archive_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_archive_flag'] = $oArchiveFlagAttDef;
}
if (MetaModel::IsObsoletable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_obsolescence_flag'] = $oObsoleteFlagAttDef;
}
if (MetaModel::IsObsoletable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_obsolescence_flag'] = $oObsoleteFlagAttDef;
}
}
}
// Add the friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
}
// Add the friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
if (MetaModel::IsArchivable($sClass))
{
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, 'archive_flag');
$aAttToLoadWithAttDef[$sClassAlias]['archive_flag'] = $oArchiveFlagAttDef;
}
if (MetaModel::IsObsoletable($sClass))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, 'obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias]['obsolescence_flag'] = $oObsoleteFlagAttDef;
}
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttToLoadWithAttDef[$sClassAlias]))
{
$aAttToLoadWithAttDef[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
}
if (MetaModel::IsArchivable($sClass))
{
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, 'archive_flag');
$aAttToLoadWithAttDef[$sClassAlias]['archive_flag'] = $oArchiveFlagAttDef;
}
$this->m_aAttToLoad = $aAttToLoadWithAttDef;
if (MetaModel::IsObsoletable($sClass))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, 'obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias]['obsolescence_flag'] = $oObsoleteFlagAttDef;
}
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttToLoadWithAttDef[$sClassAlias]))
{
$aAttToLoadWithAttDef[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
}
}
$this->m_aAttToLoad = $aAttToLoadWithAttDef;
}
/**
@@ -971,7 +980,15 @@ class DBObjectSet implements iDBObjectSetIterator
}
else
{
$oRetObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
try
{
$oRetObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
}
catch (CoreException $e)
{
$this->m_iCurrRow++;
$oRetObj = $this->Fetch($sRequestedClassAlias);
}
}
break;
}

View File

@@ -1,20 +1,21 @@
<?php
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* 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
*/
$bUseLegacyDBSearch = utils::GetConfig()->Get('use_legacy_dbsearch');
@@ -237,6 +238,12 @@ abstract class DBSearch
*/
abstract public function GetClassAlias();
/**
* @return string
* @internal
*/
abstract public function GetFirstJoinedClass();
/**
* Change the class
*
@@ -271,6 +278,10 @@ abstract class DBSearch
*/
abstract public function RenameAlias($sOldName, $sNewName);
abstract public function RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation = array());
abstract public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true);
/**
* @internal
* @return mixed
@@ -433,11 +444,27 @@ abstract class DBSearch
*/
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
/**
/**
* Filter this search with another search.
* Initial search is unmodified.
* The difference with Intersect, is that an alias can be provided,
* the filtered class does not need to be the first joined class,
* it can be any class of the search.
*
* @param string $sClassAlias class being filtered
* @param DBSearch $oFilter Filter to apply
*
* @return DBSearch The filtered search
* @throws \CoreException
*/
abstract public function Filter($sClassAlias, DBSearch $oFilter);
/**
* Filter the result
*
* The filter is performed by returning only the values in common with the given $oFilter
* The impact on the resulting query performance/viability can be significant.
* Only the first joined class can be filtered.
*
* @internal
*
@@ -456,7 +483,7 @@ abstract class DBSearch
*
* @param DBSearch $oFilter The join is performed against $oFilter selected class
* @param integer $iDirection can be either DBSearch::JOIN_POINTING_TO or DBSearch::JOIN_REFERENCED_BY
* @param string $sExtKeyAttCode The join is performed against $sExtKeyAttCode wetheir it is compared aginst the current DBSearch or $oFilter depend of $iDirection
* @param string $sExtKeyAttCode The join is performed against $sExtKeyAttCode whether it is compared against the current DBSearch or $oFilter depend of $iDirection
* @param integer $iOperatorCode See DBSearch::AddCondition_PointingTo()
* @param array|null $aRealiasingMap Map of aliases from the attached query, that could have been renamed by the optimization process
*
@@ -683,6 +710,15 @@ abstract class DBSearch
*/
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false);
/**
* Export the DBSearch as a structure (array of arrays...) suitable for a conversion to JSON
*
* @internal
*
* @return mixed[string]
*/
abstract public function ToJSON();
static protected $m_aOQLQueries = array();
/**
@@ -718,12 +754,13 @@ abstract class DBSearch
*
* @param string $sQuery The OQL to convert to a DBSearch
* @param mixed[string] $aParams array of <mixed> params index by <string> name
* @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL
*
* @return DBObjectSearch|DBUnionSearch
*
* @throws OQLException
*/
static public function FromOQL($sQuery, $aParams = null)
static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
{
if (empty($sQuery))
{
@@ -765,7 +802,10 @@ abstract class DBSearch
$oOql = new OqlInterpreter($sQuery);
$oOqlQuery = $oOql->ParseQuery();
$oMetaModel = new ModelReflectionRuntime();
if ($oMetaModel === null)
{
$oMetaModel = new ModelReflectionRuntime();
}
$oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue
$oResultFilter = $oOqlQuery->ToDBSearch($sQuery);
@@ -945,7 +985,9 @@ abstract class DBSearch
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL, $aOrderBy, $iLimitCount, $iLimitStart);
}
catch (Exception $e)
// Catch CoreException to add info before throwing again
// Other exceptions will be thrown directly
catch (CoreException $e)
{
// Add some information...
$e->addInfo('OQL', $this->ToOQL());
@@ -1055,6 +1097,39 @@ abstract class DBSearch
return $sRes;
}
/**
* @param bool $bMustHaveOneResultMax if true will throw a CoreOqlMultipleResultsFound if multiple results
* @param array $aOrderBy
* @param array $aSearchParams
*
* @return null|\DBObject query result
* @throws \CoreOqlMultipleResultsForbiddenException if multiple results found and parameter enforce the check
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function GetFirstResult($bMustHaveOneResultMax = true, $aOrderBy = array(), $aSearchParams = array())
{
$oSet = new DBObjectSet($this, array(), $aSearchParams, null, 2);
$oFirstResult = $oSet->Fetch();
if ($oFirstResult === null) // useless but here for readability ;)
{
return null;
}
if ($bMustHaveOneResultMax)
{
$oSecondResult = $oSet->Fetch();
if ($oSecondResult !== null)
{
throw new CoreOqlMultipleResultsForbiddenException(
'Search returned multiple results, this is forbidden. Query was: '.$this->ToOQL());
}
}
return $oFirstResult;
}
/**
* @internal
* @return mixed
@@ -1067,27 +1142,28 @@ abstract class DBSearch
*/
protected abstract function SetDataFiltered();
/**
* @internal
*
* @param $aOrderBy
* @param $aArgs
* @param $aAttToLoad
* @param $aExtendedDataSpec
* @param $iLimitCount
* @param $iLimitStart
* @param $bGetCount
* @param null $aGroupByExpr
* @param null $aSelectExpr
*
* @return mixed
*/
/**
* @param $aOrderBy
* @param $aArgs
* @param $aAttToLoad
* @param $aExtendedDataSpec
* @param $iLimitCount
* @param $iLimitStart
* @param $bGetCount
* @param null $aGroupByExpr
* @param null $aSelectExpr
*
* @return SQLObjectQuery
* @throws \CoreException
* @internal
*
*/
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
{
$oSearch = $this;
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
{
foreach ($this->GetSelectedClasses() as $sClass)
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass)
{
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false)
@@ -1098,7 +1174,7 @@ abstract class DBSearch
if (is_object($oVisibleObjects))
{
$oVisibleObjects->AllowAllData();
$oSearch = $this->Intersect($oVisibleObjects);
$oSearch = $this->Filter($sClassAlias, $oVisibleObjects);
/** @var DBSearch $oSearch */
$oSearch->SetDataFiltered();
}

View File

@@ -161,6 +161,11 @@ class DBUnionSearch extends DBSearch
return $this->aSearches;
}
public function GetFirstJoinedClass()
{
return $this->GetClass();
}
/**
* Limited to the selected classes
*/
@@ -258,6 +263,24 @@ class DBUnionSearch extends DBSearch
return $bRet;
}
public function RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation = array())
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation);
}
}
public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->TranslateConditions($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
public function IsAny()
{
$bIsAny = true;
@@ -383,6 +406,16 @@ class DBUnionSearch extends DBSearch
}
}
public function Filter($sClassAlias, DBSearch $oFilter)
{
$aSearches = array();
foreach ($this->aSearches as $oSearch)
{
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
}
return new DBUnionSearch($aSearches);
}
public function Intersect(DBSearch $oFilter)
{
$aSearches = array();
@@ -453,6 +486,20 @@ class DBUnionSearch extends DBSearch
return $sRet;
}
/**
* {@inheritDoc}
* @see DBSearch::ToJSON()
*/
public function ToJSON()
{
$sRet = array('unions' => array());
foreach ($this->aSearches as $oSearch)
{
$sRet['unions'][] = $oSearch->ToJSON();
}
return $sRet;
}
/**
* Returns a new DBUnionSearch object where duplicates queries have been removed based on their OQLs
*
@@ -652,6 +699,8 @@ class DBUnionSearch extends DBSearch
}
}
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
{
$sInParamName = $this->GenerateUniqueParamName();

View File

@@ -192,7 +192,11 @@ class Dict
/**
* Formats a localized string with numbered placeholders (%1$s...) for the additional arguments
* See vsprintf for more information about the syntax of the placeholders
*
* @see \TemplateString to use placeholders
*
* @param string $sFormatCode
*
* @return string
*/
public static function Format($sFormatCode /*, ... arguments ....*/)

View File

@@ -265,7 +265,7 @@ class DisplayableNode extends GraphNode
/**
* Retrieves the list of neighbour nodes, in the given direction: 'up' or 'down'
* @param bool $bDirectionDown
* @return multitype:NULL
* @return mixed|NULL
*/
protected function GetNextNodes($bDirectionDown = true)
{
@@ -1418,16 +1418,24 @@ class DisplayableGraph extends SimpleGraph
}
return $aContextDefs;
}
/**
* Display the graph inside the given page, with the "filter" drawer above it
*
* @param WebPage $oP
* @param hash $aResults
* @param array $aResults
* @param string $sRelation
* @param ApplicationContext $oAppContext
* @param array $aExcludedObjects
* @param string $sObjClass
* @param int $iObjKey
* @param string $sContextKey
* @param array $aContextParams
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array(), $sObjClass = null, $iObjKey = null, $sContextKey, $aContextParams = array())
function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array())
{
$aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
$aExcludedByClass = array();
@@ -1611,4 +1619,4 @@ EOF
);
}
}
}

View File

@@ -1,29 +1,20 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Persistent class Event and derived
* Application internal events
* There is also a file log
* Copyright (C) 2013-2020 Combodo SARL
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* 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
*/
class Event extends DBObject implements iDisplay
@@ -101,7 +92,7 @@ class Event extends DBObject implements iDisplay
//$this->DisplayBareHeader($oPage, $bEditMode);
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
$oPage->SetCurrentTab('UI:PropertiesTab');
$this->DisplayBareProperties($oPage, $bEditMode);
}

View File

@@ -193,7 +193,7 @@ EOF
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormTagSet)
else if ($value instanceOf ormSet)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
@@ -386,4 +386,4 @@ EOF
{
return array('xlsx' => Dict::S('Core:BulkExport:XLSXFormat'));
}
}
}

View File

@@ -183,7 +183,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'h4' => array('style'),
'nav' => array('style'),
'section' => array('style'),
'code' => array('style'),
'code' => array('style', 'class'),
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
'thead' => array('style'),
'tbody' => array('style'),
@@ -206,6 +206,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'q' => array(),
'hr' => array('style'),
'pre' => array(),
'center' => array(),
);
protected static $aAttrsWhiteList = array(
@@ -240,6 +241,8 @@ class HTMLDOMSanitizer extends HTMLSanitizer
public function __construct()
{
parent::__construct();
// Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value.
// eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world"
if (!array_key_exists('href', self::$aAttrsWhiteList))
@@ -271,6 +274,8 @@ class HTMLDOMSanitizer extends HTMLSanitizer
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
// therefore we have to do the transformation upfront
$sHTML = preg_replace('@<o:p>(\s|&nbsp;)*</o:p>@', '<br>', $sHTML);
// Replace badly encoded non breaking space
$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
@@ -394,4 +399,4 @@ class HTMLDOMSanitizer extends HTMLSanitizer
}
return true;
}
}
}

120
core/iTopConfigParser.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
/**
* Created by Bruno DA SILVA, working for Combodo
* Date: 31/12/2019
* Time: 12:29
*/
use PhpParser\Node\Expr\Assign;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
class iTopConfigParser
{
/** @var \PhpParser\Node[] */
private $aInitialNodes;
/** @var \PhpParser\Node[] */
private $aVisitedNodes;
/** @var string|null */
private $oException = null;
/**
* @var array
*/
private $aVarsMap;
/**
* iTopConfigValidator constructor.
*
* @param $sConfig
* @param \PhpParser\Parser|null $oParser
*
* @throws \Exception
*/
public function __construct($sConfig)
{
$oParser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$this->aVarsMap = array(
'MySettings' => array(),
'MyModuleSettings' => array(),
'MyModules' => array(),
);
if ($sConfig !== null)
{
$this->BrowseFile($oParser, $sConfig);
}
}
/**
* @return array
*/
public function GetVarsMap()
{
return $this->aVarsMap;
}
/**
* @param $arrayName
* @param $key
*
* @return array
*/
public function GetVarValue($arrayName, $key)
{
if (!array_key_exists($arrayName, $this->aVarsMap)){
return array('found' => false);
}
$arrayValue = $this->aVarsMap[$arrayName];
if (!array_key_exists($key, $arrayValue)){
return array('found' => false);
}
return array('found' => true,
'value' => $arrayValue[$key]);
}
/**
* @param \PhpParser\Parser $oParser
* @param $sConfig
*
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
*/
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
{
$prettyPrinter = new Standard();
try
{
$aNodes = $oParser->parse($sConfig);
}
catch (\Error $e)
{
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
$this->oException = new \Exception($sMessage, 0, $e);
}
foreach ($aNodes as $oAssignation)
{
if (! $oAssignation instanceof Assign)
{
continue;
}
$sCurrentRootVar = $oAssignation->var->name;
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
{
continue;
}
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
foreach ($oAssignation->expr->items as $oItem)
{
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
}
}
}
}

View File

@@ -356,11 +356,11 @@ class ExecutionKPI
}
}
const HtmlReportFile = 'log/kpi.html';
const HTML_REPORT_FILE = 'log/kpi.html';
static protected function Report($sText)
{
file_put_contents(APPROOT.self::HtmlReportFile, "$sText\n", FILE_APPEND | LOCK_EX);
file_put_contents(APPROOT.self::HTML_REPORT_FILE, "$sText\n", FILE_APPEND | LOCK_EX);
}
static protected function MemStr($iMemory)

View File

@@ -52,7 +52,12 @@ class DBObjectSearch extends DBSearch
protected $m_bAllowAllData = false;
protected $m_bDataFiltered = false;
/**
public function ToJSON()
{
return '{}';
}
/**
* DBObjectSearch constructor.
*
* @api
@@ -291,6 +296,16 @@ class DBObjectSearch extends DBSearch
return $bFound;
}
public function RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation = array())
{
}
public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
@@ -531,7 +546,7 @@ class DBObjectSearch extends DBSearch
*
* @throws \CoreException
*
* @since 2.5 N°1418
* @since 2.5.0 N°1418
*/
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
{
@@ -955,7 +970,7 @@ class DBObjectSearch extends DBSearch
$this->RecomputeClassList($this->m_aClasses);
}
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
{
$sForeignClass = $oFilter->GetClass();
@@ -982,10 +997,115 @@ class DBObjectSearch extends DBSearch
}
}
/**
* Filter this search with another search.
* Initial search is unmodified.
* The difference with Intersect, is that an alias can be provided,
* the filtered class does not need to be the first joined class.
*
* @param string $sClassAlias class being filtered
* @param DBSearch $oFilter Filter to apply
*
* @return DBSearch The filtered search
* @throws \CoreException
*/
public function Filter($sClassAlias, DBSearch $oFilter)
{
// If the conditions are the correct ones for Intersect
if (($this->GetFirstJoinedClassAlias() == $sClassAlias))
{
return $this->Intersect($oFilter);
}
/** @var \DBObjectSearch $oFilteredSearch */
$oFilteredSearch = $this->DeepClone();
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses);
if ($oFilterExpression === false)
{
throw new CoreException("Limitation: cannot filter search");
}
$oFilteredSearch->AddConditionExpression($oFilterExpression);
return $oFilteredSearch;
}
/**
* Filter "in place" the search (filtered part is replaced in the initial search)
*
* @param DBObjectSearch $oSearch Search to filter, modified with the given filter
* @param string $sClassAlias class to filter
* @param \DBSearch $oFilter Filter to apply
*
* @return \Expression|false
* @throws \CoreException
*/
private static function FilterSubClass(DBObjectSearch &$oSearch, $sClassAlias, DBSearch $oFilter, $aRootClasses)
{
if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias))
{
$oSearch->ResetCondition();
$oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses);
return $oSearch->GetCriteria();
}
/** @var Expression $oFilterExpression */
// Search in the filter tree where is the correct DBSearch
foreach ($oSearch->m_aPointingTo as $sExtKey => $aPointingTo)
{
foreach ($aPointingTo as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $index => $oExtFilter)
{
$oFilterExpression = self::FilterSubClass($oExtFilter, $sClassAlias, $oFilter, $aRootClasses);
if ($oFilterExpression !== false)
{
$oSearch->m_aPointingTo[$sExtKey][$iOperatorCode][$index] = $oExtFilter;
return $oFilterExpression;
}
}
}
}
foreach($oSearch->m_aReferencedBy as $sForeignClass => $aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $index => $oForeignFilter)
{
$oFilterExpression = self::FilterSubClass($oForeignFilter, $sClassAlias, $oFilter, $aRootClasses);
if ($oFilterExpression !== false)
{
$oSearch->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][$index] = $oForeignFilter;
return $oFilterExpression;
}
}
}
}
}
return false;
}
/**
* @inheritDoc
* @throws \CoreException
*/
public function Intersect(DBSearch $oFilter)
{
return $this->IntersectSubClass($oFilter, $this->m_aClasses);
}
/**
* @param \DBSearch $oFilter
* @param array $aRootClasses classes of the root search (for aliases)
*
* @return \DBUnionSearch|mixed
* @throws \CoreException
*/
protected function IntersectSubClass(DBSearch $oFilter, $aRootClasses)
{
if ($oFilter instanceof DBUnionSearch)
{
@@ -1001,15 +1121,12 @@ class DBObjectSearch extends DBSearch
foreach ($aFilters as $oRightFilter)
{
// Limitation: the queried class must be the first declared class
if ($this->GetFirstJoinedClassAlias() != $this->GetClassAlias())
{
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$this->GetClass()} AS {$this->GetClassAlias()}) is not the first joined class ({$this->GetFirstJoinedClass()} AS {$this->GetFirstJoinedClassAlias()})");
}
if ($oRightFilter->GetFirstJoinedClassAlias() != $oRightFilter->GetClassAlias())
{
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$oRightFilter->GetClass()} AS {$oRightFilter->GetClassAlias()}) is not the first joined class ({$oRightFilter->GetFirstJoinedClass()} AS {$oRightFilter->GetFirstJoinedClassAlias()})");
}
/** @var \DBObjectSearch $oLeftFilter */
$oLeftFilter = $this->DeepClone();
$oRightFilter = $oRightFilter->DeepClone();
@@ -1019,14 +1136,14 @@ class DBObjectSearch extends DBSearch
$oLeftFilter->AllowAllData();
}
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass())
{
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass()))
{
// Specialize $oLeftFilter
$oLeftFilter->ChangeClass($oRightFilter->GetClass());
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
}
elseif (MetaModel::IsParentClass($oRightFilter->GetClass(), $oLeftFilter->GetClass()))
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
{
// Specialize $oRightFilter
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
@@ -1038,7 +1155,7 @@ class DBObjectSearch extends DBSearch
}
$aAliasTranslation = array();
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $oLeftFilter->m_aClasses, $aAliasTranslation);
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
$aSearches[] = $oLeftFilter;
}
@@ -1561,6 +1678,25 @@ class DBObjectSearch extends DBSearch
return $sRet;
}
/**
* Generate an INSERT statement.
* Note : unlike `RenderUpdate` and `RenderSelect`, it is limited to one and only one table.
*
* @param array $aValues is an array of $sAttCode => $value
* @param array $aArgs
*
* @return string
* @throws \CoreException
*/
public function MakeInsertQuery($aValues, $aArgs = array())
{
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this);
$oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectUpdateQuery($aValues);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
$sRet = $oSQLQuery->RenderInsert($aScalarArgs);
return $sRet;
}
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
// Hide objects that are not visible to the current user
@@ -2465,4 +2601,6 @@ class DBObjectSearch extends DBSearch
}
return $oExpression;
}
}

View File

@@ -18,24 +18,36 @@
/**
* @since 2.7.0 N°2518
* @since 2.7.0 N°2518 N°2793
*/
interface ILogFileNameBuilder
interface iLogFileNameBuilder
{
public function __construct($sFileFullPath);
/**
* @param string $sLogFileFullPath full path name for the log file
*/
public function __construct($sLogFileFullPath = null);
/**
* @return string log file path we will write new log entry to
*/
public function GetLogFilePath();
}
class DefaultLogFileNameBuilder implements ILogFileNameBuilder
class DefaultLogFileNameBuilder implements iLogFileNameBuilder
{
private $sLogFileFullPath;
public function __construct($sFileFullPath)
/**
* @inheritDoc
*/
public function __construct($sLogFileFullPath = null)
{
$this->sLogFileFullPath = $sFileFullPath;
$this->sLogFileFullPath = $sLogFileFullPath;
}
/**
* @inheritDoc
*/
public function GetLogFilePath()
{
return $this->sLogFileFullPath;
@@ -45,60 +57,365 @@ class DefaultLogFileNameBuilder implements ILogFileNameBuilder
/**
* Adds a suffix to the filename
*
* @since 2.7.0 N°2518
* @since 2.7.0 N°2518 N°2793
*/
abstract class RotatingLogFileNameBuilder implements ILogFileNameBuilder
abstract class RotatingLogFileNameBuilder implements iLogFileNameBuilder
{
/**
* Test is done each time to cover edge case like session beginning at 23:59 and ending at 00:01
* We are caching the file mtime though
* @var array with full file path as key and DateTime (file last modification time) as value
*/
protected static $aLogFileLastModified = array();
/** @var string */
protected $sLogFileFullPath;
/** @var string */
protected $sFilePath;
/** @var string */
protected $sFileBaseName;
/** @var string */
protected $sFileExtension;
public function __construct($sFileFullPath)
/**
* @inheritDoc
*/
public function __construct($sLogFileFullPath = null)
{
$aPathParts = pathinfo($sFileFullPath);
$this->sLogFileFullPath = $sLogFileFullPath;
}
protected function GetLastModifiedDateForFile()
{
if (isset(static::$aLogFileLastModified[$this->sLogFileFullPath]))
{
return static::$aLogFileLastModified[$this->sLogFileFullPath];
}
return null;
}
protected function SetLastModifiedDateForFile($oDateTime)
{
static::$aLogFileLastModified[$this->sLogFileFullPath] = $oDateTime;
}
/**
* Need to be called when the file is rotated : actually the next call will need to check on the real date modified instead of using
* the previously cached value !
*/
public function ResetLastModifiedDateForFile()
{
static::$aLogFileLastModified[$this->sLogFileFullPath] = null;
}
/**
* @inheritDoc
*
* Doing the check before opening and writing the log file. There is also a iProcess but cron can be disabled...
*
* @see \LogFileRotationProcess the iProcess impl
*/
public function GetLogFilePath()
{
$this->CheckAndRotateLogFile();
return $this->sLogFileFullPath;
}
/**
* Check log last date modified. If too old then rotate the log file (move it to a new name with a suffix)
*
* @uses filemtime() to get log file date last modified
*/
public function CheckAndRotateLogFile()
{
$oConfig = utils::GetConfig();
$sItopTimeZone = $oConfig->Get('timezone');
$timezone = new DateTimeZone($sItopTimeZone);
if ($this->GetLastModifiedDateForFile() === null)
{
if (!$this->IsLogFileExists())
{
return;
}
$iLogDateLastModifiedTimeStamp = filemtime($this->sLogFileFullPath);
if ($iLogDateLastModifiedTimeStamp === false)
{
return;
}
$oDateTime = DateTime::createFromFormat('U', $iLogDateLastModifiedTimeStamp);
$oDateTime->setTimezone($timezone);
$this->SetLastModifiedDateForFile($oDateTime);
}
$oNow = new DateTime('now', $timezone);
$bShouldRotate = $this->ShouldRotate($this->GetLastModifiedDateForFile(), $oNow);
if (!$bShouldRotate)
{
return;
}
$this->RotateLogFile($this->GetLastModifiedDateForFile());
}
/**
* Rotate current log file
*
* @param DateTime $oLogFileLastModified date when the log file was last modified
*
* @uses \iTopMutex instead of flock as doing a rename on a file with a flock cause an error on PHP 5.6.40 Windows (ok on 7.3.15 though)
* @uses GetRotatedFileName to get rotated file name
*/
protected function RotateLogFile($oLogFileLastModified)
{
if (!$this->IsLogFileExists()) // extra check, but useful for cron also !
{
return;
}
$oLock = null;
try
{
$oLock = new iTopMutex('log_rotation_'.$this->sLogFileFullPath);
$oLock->Lock();
if (!$this->IsLogFileExists()) // extra extra check if we were blocked and another process moved the file in the meantime
{
$oLock->Unlock();
return;
}
$this->ResetLastModifiedDateForFile();
$sNewLogFileName = $this->GetRotatedFileName($oLogFileLastModified);
rename($this->sLogFileFullPath, $sNewLogFileName);
}
catch (Exception $e)
{
// nothing to do, cannot log... file will be renamed on the next call O:)
return;
}
finally
{
if (!is_null($oLock)) { $oLock->Unlock();}
}
}
/**
* @param DateTime $oLogFileLastModified date when the log file was last modified
*
* @return string the full path of the rotated log file
* @uses static::$oLogFileLastModified
* @uses GetFileSuffix
*/
public function GetRotatedFileName($oLogFileLastModified)
{
$aPathParts = pathinfo($this->sLogFileFullPath);
$this->sFilePath = $aPathParts['dirname'];
$this->sFileBaseName = $aPathParts['filename'];
$this->sFileExtension = $aPathParts['extension'];
}
public function GetLogFilePath()
{
$sFileSuffix = $this->GetFileSuffix();
$sFileSuffix = $this->GetFileSuffix($oLogFileLastModified);
return $this->sFilePath
.'/'
return $this->sFilePath.DIRECTORY_SEPARATOR
.$this->sFileBaseName
.'.'.$sFileSuffix
.'.'.$this->sFileExtension;
}
abstract protected function GetFileSuffix();
/**
* @return bool true if file exists and is readable
*/
public function IsLogFileExists()
{
if (!file_exists($this->sLogFileFullPath))
{
return false;
}
if (!is_readable($this->sLogFileFullPath))
{
return false;
}
return true;
}
/**
* **Warning :** both DateTime params must have the same timezone set ! Should use the iTop timezone ('timezone' config parameter)
*
* @param DateTime $oLogFileLastModified date when the log file was last modified
* @param DateTime $oNow date/time of the log we want to write
*
* @return bool true if the file has older informations and we need to move it to an archive (rotate), false if we don't have to
*/
abstract public function ShouldRotate($oLogFileLastModified, $oNow);
/**
* @param DateTime $oDate log file last modification date
*
* @return string suffix for the rotated log file
*/
abstract protected function GetFileSuffix($oDate);
/**
* @see \LogFileRotationProcess
*
* @param \DateTime $oNow current date
*
* @return DateTime time when the cron process should run
*/
abstract public function GetCronProcessNextOccurrence(DateTime $oNow);
}
/**
* @since 2.7.0 N°2518
* @since 2.7.0 N°2518 N°2793
*/
class DailyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
{
protected function GetFileSuffix()
/**
* @inheritDoc
*/
protected function GetFileSuffix($oDate)
{
return date('Y-m-d');
return $oDate->format('Y-m-d');
}
/**
* @inheritDoc
*/
public function ShouldRotate($oLogFileLastModified, $oNow)
{
$iLogYear = $oLogFileLastModified->format('Y');
$iLogDay = $oLogFileLastModified->format('z');
$iNowYear = $oNow->format('Y');
$iNowDay = $oNow->format('z');
if ($iLogYear !== $iNowYear)
{
return true;
}
if ($iLogDay !== $iNowDay)
{
return true;
}
return false;
}
/**
* @inheritDoc
*/
public function GetCronProcessNextOccurrence(DateTime $oNow)
{
$oOccurrence = clone $oNow;
$oOccurrence->modify('tomorrow');
return $oOccurrence;
}
}
/**
* @since 2.7.0 N°2518
* @since 2.7.0 N°2518 N°2793
*/
class WeeklyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
{
protected function GetFileSuffix()
/**
* @inheritDoc
*/
protected function GetFileSuffix($oDate)
{
$sWeekYear = date('o');
$sWeekNumber = date('W');
$sWeekYear = $oDate->format('o');
$sWeekNumber = $oDate->format('W');
return $sWeekYear.'-week'.$sWeekNumber;
}
/**
* @inheritDoc
*/
public function ShouldRotate($oLogFileLastModified, $oNow)
{
$iLogYear = $oLogFileLastModified->format('Y');
$iLogWeek = $oLogFileLastModified->format('W');
$iNowYear = $oNow->format('Y');
$iNowWeek = $oNow->format('W');
if ($iLogYear !== $iNowYear)
{
return true;
}
if ($iLogWeek !== $iNowWeek)
{
return true;
}
return false;
}
/**
* @inheritDoc
*/
public function GetCronProcessNextOccurrence(DateTime $oNow)
{
$oOccurrence = clone $oNow;
$oOccurrence->modify('Monday next week');
$oOccurrence->setTime(0, 0, 0);
return $oOccurrence;
}
}
/**
* @since 2.7.0 N°2820
*/
class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
{
/**
* @inheritDoc
*/
public function ShouldRotate($oLogFileLastModified, $oNow)
{
$iLogYear = $oLogFileLastModified->format('Y');
$iLogMonth = $oLogFileLastModified->format('n');
$iNowYear = $oNow->format('Y');
$iNowMonth = $oNow->format('n');
if ($iLogYear !== $iNowYear)
{
return true;
}
if ($iLogMonth !== $iNowMonth)
{
return true;
}
return false;
}
/**
* @inheritDoc
*/
protected function GetFileSuffix($oDate)
{
$sWeekYear = $oDate->format('o');
$sWeekNumber = $oDate->format('m');
return $sWeekYear.'-month'.$sWeekNumber;
}
/**
* @inheritDoc
*/
public function GetCronProcessNextOccurrence(DateTime $oNow)
{
$oOccurrence = clone $oNow;
$oOccurrence->modify('first day of next month');
$oOccurrence->setTime(0, 0, 0);
return $oOccurrence;
}
}
/**
@@ -111,7 +428,7 @@ class LogFileNameBuilderFactory
*
* @param string $sFileFullPath
*
* @return \ILogFileNameBuilder
* @return \iLogFileNameBuilder
* @throws \ConfigException
* @throws \CoreException
*/
@@ -119,7 +436,7 @@ class LogFileNameBuilderFactory
{
$oConfig = utils::GetConfig();
$sFileNameBuilderImpl = $oConfig->Get('log_filename_builder_impl');
if (empty($sFileNameBuilderImpl))
if (!is_a($sFileNameBuilderImpl, iLogFileNameBuilder::class, true))
{
$sFileNameBuilderImpl = 'DefaultLogFileNameBuilder';
}
@@ -134,7 +451,7 @@ class LogFileNameBuilderFactory
*
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @since 2.7.0 allow to rotate file (N°2518)
* @since 2.7.0 N°2518 N°2793 file log rotation
*/
class FileLog
{
@@ -153,28 +470,42 @@ class FileLog
$this->oFileNameBuilder = LogFileNameBuilderFactory::GetInstance($sFileName);
}
public function Error($sText)
public function Error($sText, $sChannel = '', $aContext = array())
{
$this->Write('Error | '.$sText);
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
public function Warning($sText)
public function Warning($sText, $sChannel = '', $aContext = array())
{
$this->Write('Warning | '.$sText);
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
public function Info($sText)
public function Info($sText, $sChannel = '', $aContext = array())
{
$this->Write('Info | '.$sText);
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
public function Ok($sText)
public function Ok($sText, $sChannel = '', $aContext = array())
{
$this->Write('Ok | '.$sText);
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
protected function Write($sText)
public function Debug($sText, $sChannel = '', $aContext = array())
{
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
public function Trace($sText, $sChannel = '', $aContext = array())
{
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
}
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
{
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
if (empty($sLogFilePath))
@@ -187,7 +518,15 @@ class FileLog
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
if (empty($aContext))
{
fwrite($hLogFile, "$sDate | $sText\n");
}
else
{
$sContext = var_export($aContext, true);
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
}
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
@@ -197,53 +536,234 @@ class FileLog
abstract class LogAPI
{
const CHANNEL_DEFAULT = '';
const LEVEL_ERROR = 'Error';
const LEVEL_WARNING = 'Warning';
const LEVEL_INFO = 'Info';
const LEVEL_OK = 'Ok';
const LEVEL_DEBUG = 'Debug';
const LEVEL_TRACE = 'Trace';
protected static $aLevelsPriority = array(
self::LEVEL_ERROR => 400,
self::LEVEL_WARNING => 300,
self::LEVEL_INFO => 200,
self::LEVEL_OK => 200,
self::LEVEL_DEBUG => 100,
self::LEVEL_TRACE => 50,
);
protected static $m_oMockMetaModelConfig = null;
public static function Enable($sTargetFile)
{
// m_oFileLog is not defined as a class attribute so that each impl will have its own
static::$m_oFileLog = new FileLog($sTargetFile);
}
public static function Error($sText)
public static function MockStaticObjects($oFileLog, $oMetaModelConfig=null)
{
if (static::$m_oFileLog)
{
static::$m_oFileLog->Error($sText);
}
static::$m_oFileLog = $oFileLog;
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
}
public static function Warning($sText)
public static function Error($sMessage, $sChannel = null, $aContext = array())
{
if (static::$m_oFileLog)
{
static::$m_oFileLog->Warning($sText);
}
static::Log(self::LEVEL_ERROR, $sMessage, $sChannel, $aContext);
}
public static function Info($sText)
public static function Warning($sMessage, $sChannel = null, $aContext = array())
{
if (static::$m_oFileLog)
{
static::$m_oFileLog->Info($sText);
}
static::Log(self::LEVEL_WARNING, $sMessage, $sChannel, $aContext);
}
public static function Ok($sText)
public static function Info($sMessage, $sChannel = null, $aContext = array())
{
if (static::$m_oFileLog)
{
static::$m_oFileLog->Ok($sText);
}
static::Log(self::LEVEL_INFO, $sMessage, $sChannel, $aContext);
}
public static function Ok($sMessage, $sChannel = null, $aContext = array())
{
static::Log(self::LEVEL_OK, $sMessage, $sChannel, $aContext);
}
public static function Debug($sMessage, $sChannel = null, $aContext = array())
{
static::Log(self::LEVEL_DEBUG, $sMessage, $sChannel, $aContext);
}
public static function Trace($sMessage, $sChannel = null, $aContext = array())
{
static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext);
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
{
if (! static::$m_oFileLog)
{
return;
}
if (! isset(self::$aLevelsPriority[$sLevel]))
{
IssueLog::Error("invalid log level '{$sLevel}'");
return;
}
if (is_null($sChannel))
{
$sChannel = static::CHANNEL_DEFAULT;
}
$sMinLogLevel = self::GetMinLogLevel($sChannel);
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
{
return;
}
if (is_string($sMinLogLevel))
{
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
{
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
}
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
{
//priority too low regarding the conf, do not log this
return;
}
}
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
}
/**
* @param $sChannel
*
* @return mixed|null
*/
private static function GetMinLogLevel($sChannel)
{
$oConfig = (static::$m_oMockMetaModelConfig !== null) ? static::$m_oMockMetaModelConfig : \MetaModel::GetConfig();
if (!$oConfig instanceof Config)
{
return self::LEVEL_OK;
}
$sLogLevelMin = $oConfig->Get('log_level_min');
if (empty($sLogLevelMin))
{
return self::LEVEL_OK;
}
if (!is_array($sLogLevelMin))
{
return $sLogLevelMin;
}
if (isset($sLogLevelMin[$sChannel]))
{
return $sLogLevelMin[$sChannel];
}
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT]))
{
return $sLogLevelMin[$sChannel];
}
return self::LEVEL_OK;
}
}
class SetupLog extends LogAPI
{
const CHANNEL_DEFAULT = 'SetupLog';
protected static $m_oFileLog = null;
}
class IssueLog extends LogAPI
{
const CHANNEL_DEFAULT = 'IssueLog';
protected static $m_oFileLog = null;
}
class ToolsLog extends LogAPI
{
const CHANNEL_DEFAULT = 'ToolsLog';
protected static $m_oFileLog = null;
}
class LogFileRotationProcess implements iScheduledProcess
{
/**
* Cannot get this list from anywhere as log file name is provided by the caller using LogAPI::Enable
* @var string[]
*/
const LOGFILES_TO_ROTATE = array(
'setup.log',
'error.log',
'tools.log',
'itop-fence.log',
);
/**
* @inheritDoc
*/
public function Process($iUnixTimeLimit)
{
$sLogFileNameBuilder = $this->GetLogFileNameBuilderClassName();
foreach (self::LOGFILES_TO_ROTATE as $sLogFileName)
{
$sLogFileFullPath = APPROOT
.DIRECTORY_SEPARATOR.'log'
.DIRECTORY_SEPARATOR.$sLogFileName;
/** @var \RotatingLogFileNameBuilder $oLogFileNameBuilder */
$oLogFileNameBuilder = new $sLogFileNameBuilder($sLogFileFullPath);
$oLogFileNameBuilder->ResetLastModifiedDateForFile();
$oLogFileNameBuilder->CheckAndRotateLogFile();
}
}
/**
* @inheritDoc
*/
public function GetNextOccurrence()
{
try
{
$sLogFileNameBuilder = $this->GetLogFileNameBuilderClassName();
}
catch (ProcessException $e)
{
return new DateTime('3000-01-01');
}
/** @var \RotatingLogFileNameBuilder $oLogFileNameBuilder */
$oLogFileNameBuilder = new $sLogFileNameBuilder();
return $oLogFileNameBuilder->GetCronProcessNextOccurrence(new DateTime());
}
/**
* @return string RotatingLogFileNameBuilder implementation configured
* @throws \ProcessException if the class is invalid
*/
private function GetLogFileNameBuilderClassName()
{
$sLogFileNameBuilder = MetaModel::GetConfig()->Get('log_filename_builder_impl');
if (is_a($sLogFileNameBuilder, RotatingLogFileNameBuilder::class, true))
{
return $sLogFileNameBuilder;
}
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
}
}

View File

@@ -27,7 +27,7 @@ require_once APPROOT.'core/expressioncache.class.inc.php';
/**
* We need to have all iLoginFSMExtension/iLoginDataExtension impl loaded ! Cannot use autoloader...
* We need to have all iLoginFSMExtension/iLoginUIExtension impl loaded ! Cannot use autoloader...
*/
require_once APPROOT.'application/loginform.class.inc.php';
require_once APPROOT.'application/loginbasic.class.inc.php';
@@ -543,7 +543,7 @@ abstract class MetaModel
*
* @return array rule id as key, rule properties as value
* @throws \CoreException
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
* @see #SetUniquenessRuleRootClass that fixes a specific 'root_class' property to know which class is root per rule
*/
final public static function GetUniquenessRules($sClass, $bClassDefinitionOnly = false)
@@ -688,7 +688,7 @@ abstract class MetaModel
* @param array $aRuleProperties
*
* @return bool
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
*/
private static function IsUniquenessRuleContainingOnlyDisabledKey($aRuleProperties)
{
@@ -918,6 +918,20 @@ abstract class MetaModel
return self::$m_aAttribOrigins[$sClass][$sAttCode];
}
/**
* @deprecated
* @param string $sClass
* @param string $sAttCode
*
* @return mixed
* @throws \CoreException
*/
final static public function GetFilterCodeOrigin($sClass, $sAttCode)
{
self::_check_subclass($sClass);
return self::$m_aFilterOrigins[$sClass][$sAttCode];
}
/**
* @param string $sClass
* @param string $sAttCode
@@ -2495,6 +2509,32 @@ abstract class MetaModel
return array();
}
/**
* Return an hash array of the possible attribute flags (code => value)
*
* Example:
* [
* "read_only" => OPT_ATT_READONLY,
* "mandatory" => OPT_ATT_MANDATORY,
* ...
* ]
*
* @return array
* @since 2.7.0
*/
public static function EnumPossibleAttributeFlags()
{
return $aPossibleAttFlags = array(
'normal' => OPT_ATT_NORMAL,
'hidden' => OPT_ATT_HIDDEN,
'read_only' => OPT_ATT_READONLY,
'mandatory' => OPT_ATT_MANDATORY,
'must_change' => OPT_ATT_MUSTCHANGE,
'must_prompt' => OPT_ATT_MUSTPROMPT,
'slave' => OPT_ATT_SLAVE
);
}
/**
* @param string $sClass
* @param string $sState
@@ -2794,7 +2834,7 @@ abstract class MetaModel
// Build the list of available extensions
//
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginDataExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider');
$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();
@@ -3201,7 +3241,7 @@ abstract class MetaModel
*
* @throws \CoreUnexpectedValue if the rule is invalid
*
* @since 2.6 N°659 uniqueness constraint
* @since 2.6.0 N°659 uniqueness constraint
* @since 2.6.1 N°1968 (joli mois de mai...) disallow overrides of 'attributes' properties
*/
public static function CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bRuleOverride = true, $aExistingClassFields = array())
@@ -5087,7 +5127,7 @@ abstract class MetaModel
}
/**
* Determines wether the target DB is frozen or not
* Determines whether the target DB is frozen or not
*
* @return bool
*/
@@ -5514,10 +5554,10 @@ abstract class MetaModel
//
$bToBeChanged = false;
$sActualFieldSpec = CMDBSource::GetFieldSpec($sTable, $sField);
if (strcasecmp($sDBFieldSpec, $sActualFieldSpec) != 0)
if (!CMDBSource::IsSameFieldTypes($sDBFieldSpec, $sActualFieldSpec))
{
$bToBeChanged = true;
$aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldSpec' while expecting '$sDBFieldSpec'";
$aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found <code>$sActualFieldSpec</code> while expecting <code>$sDBFieldSpec</code>";
}
if ($bToBeChanged)
{
@@ -5661,10 +5701,14 @@ abstract class MetaModel
{
$sChangeList = implode(', ', $aChangeList);
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
}
foreach($aPostTableAlteration as $sTable => $aChangeList)
{
$aCondensedQueries = array_merge($aCondensedQueries, $aChangeList);
// Add request right after the ALTER TABLE
if (isset($aPostTableAlteration[$sTable]))
{
foreach ($aPostTableAlteration[$sTable] as $sQuery)
{
$aCondensedQueries[] = $sQuery;
}
}
}
return array($aErrors, $aSugFix, $aCondensedQueries);
@@ -5672,6 +5716,8 @@ abstract class MetaModel
/**
* @deprecated 2.7.0 N°2369 will be removed in 2.8
*
* @return array
* @throws \CoreException
* @throws \Exception
@@ -6324,6 +6370,11 @@ abstract class MetaModel
// classes have to be derived from cmdbabstract (to be editable in the UI)
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
if (!defined('MODULESROOT'))
{
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
}
require_once(APPROOT.'core/autoload.php');
require_once(APPROOT.'env-'.self::$m_sEnvironment.'/autoload.php');
@@ -6685,6 +6736,10 @@ abstract class MetaModel
else
{
// do the job for the real target class
if (!class_exists($aRow[$sClassAlias."finalclass"]))
{
throw new CoreException("Class {$aRow[$sClassAlias."finalclass"]} derived from $sClass does not exist anymore, please remove corresponding tables in the database", array('row' => $aRow));
}
$sClass = $aRow[$sClassAlias."finalclass"];
}
return new $sClass($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
@@ -6748,7 +6803,7 @@ abstract class MetaModel
* @throws CoreException if no result found and $bMustBeFound=true
* @throws \Exception
*
* @since 2.4 introduction of the archive functionalities
* @since 2.4.0 introduction of the archive functionality
*
* @see MetaModel::GetObject() same but returns null or ArchivedObjectFoundException if object exists but is
* archived
@@ -6935,6 +6990,8 @@ abstract class MetaModel
}
/**
* @deprecated 2.7.0 N°1627, use ItopCounter::incRootClass($sClass) instead
*
* @param string $sClass
*
* @return int
@@ -6942,10 +6999,7 @@ abstract class MetaModel
*/
public static function GetNextKey($sClass)
{
$sRootClass = MetaModel::GetRootClass($sClass);
$sRootTable = MetaModel::DBGetTable($sRootClass);
return CMDBSource::GetNextInsertId($sRootTable);
return ItopCounter::IncClass($sClass);
}
/**
@@ -7073,7 +7127,7 @@ abstract class MetaModel
if ($bSkipLinkingClasses)
{
$aLinksClasses = self::EnumLinksClasses();
$aLinksClasses = array_keys(self::GetLinkClasses());
}
// 1-N links (referencing my class), array of sClass => array of sAttcode
@@ -7111,12 +7165,12 @@ abstract class MetaModel
}
/**
* @deprecated It is not recommended to use this function: call {@link MetaModel::GetLinkClasses} instead !
* @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)
*
* @see MetaModel::GetLinkClasses
*/
public static function EnumLinksClasses()
{
@@ -7145,15 +7199,16 @@ abstract class MetaModel
}
/**
* @deprecated It is not recommended to use this function: call {@link MetaModel::GetLinkClasses} instead !
* @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
*
* @param string $sClass
* @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
*
* @see MetaModel::GetLinkClasses
*/
public static function EnumLinkingClasses($sClass = "")
{
@@ -7195,14 +7250,14 @@ abstract class MetaModel
}
/**
* This function has two siblings that will be soon deprecated:
* {@link MetaModel::EnumLinkingClasses} and {@link MetaModel::EnumLinkClasses}
*
* 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
*
* @return array external key code => target class
* 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
*/
public static function GetLinkClasses()
@@ -7341,19 +7396,31 @@ abstract class MetaModel
/**
* @param string $sInterface
* @param string|null $sFilterInstanceOf [optional] if given, only instance of this string will be returned
*
* @return array classes=>instance implementing the given interface
*/
public static function EnumPlugins($sInterface)
public static function EnumPlugins($sInterface, $sFilterInstanceOf = null)
{
if (array_key_exists($sInterface, self::$m_aExtensionClasses))
{
return self::$m_aExtensionClasses[$sInterface];
}
else
if (!array_key_exists($sInterface, self::$m_aExtensionClasses))
{
return array();
}
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
);
}
/**
@@ -7511,9 +7578,12 @@ abstract class MetaModel
$sKey = self::DBGetKey($sClass);
$sRootKey = self::DBGetKey($sRootClass);
$sRootField = self::DBGetClassField($sRootClass);
// Copy the finalclass of the root table
$sRequest = "UPDATE `$sTable`,`$sRootTable` SET `$sTable`.`$sField` = `$sRootTable`.`$sRootField` WHERE `$sTable`.`$sKey` = `$sRootTable`.`$sRootKey`";
$aRequests[] = $sRequest;
if ($sTable != $sRootTable)
{
// Copy the finalclass of the root table
$sRequest = "UPDATE `$sTable`,`$sRootTable` SET `$sTable`.`$sField` = `$sRootTable`.`$sRootField` WHERE `$sTable`.`$sKey` = `$sRootTable`.`$sRootKey`";
$aRequests[] = $sRequest;
}
}
return $aRequests;

View File

@@ -88,6 +88,25 @@ class iTopMutex
$this->InitMySQLSession();
}
/**
* Get instance of self but with an escaped $sName
*
* this is meant to be used with non trusted string such as user inputs.
*
* @param mixed ...$aArgs smae as __construct()
*
* @return \iTopMutex
* @since 2.7.0
*
*/
public static function GetInstanceFromUserInput(...$aArgs)
{
$aArgs[0] = \CMDBSource::Quote($aArgs[0]);
return new iTopMutex(...$aArgs);
}
public function __destruct()
{
if ($this->bLocked)

View File

@@ -27,6 +27,17 @@ class MissingQueryArgument extends CoreException
*/
abstract class Expression
{
const OPERATOR_BINARY = 'binary';
const OPERATOR_BOOLEAN = 'boolean_binary';
const OPERATOR_FIELD = 'field';
const OPERATOR_FUNCTION = 'function';
const OPERATOR_INTERVAL = 'interval';
const OPERATOR_LIST = 'list';
const OPERATOR_SCALAR = 'scalar';
const OPERATOR_UNARY = 'unary';
const OPERATOR_VARIABLE = 'variable';
/**
* Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects)
**/
@@ -99,13 +110,23 @@ abstract class Expression
abstract public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false);
/**
* @param DBObjectSearch $oSearch
* Recursively renders the expression as a structure (array) suitable for a JSON export
*
* @param mixed[string] $aArgs
* @param boolean $bRetrofitParams
* @return mixed[string]
*/
abstract public function ToJSON(&$aArgs = null, $bRetrofitParams = false);
/**
* @param DBSearch $oSearch
* @param array $aArgs
* @param AttributeDefinition $oAttDef
*
* @param array $aCtx
*
* @return array parameters for the search form
* @throws \MissingQueryArgument
*/
public function Display($oSearch, &$aArgs = null, $oAttDef = null, &$aCtx = array())
{
@@ -145,6 +166,10 @@ abstract class Expression
return true;
}
/**
* @return string
* @throws \MissingQueryArgument
*/
public function serialize()
{
return base64_encode($this->RenderExpression(false));
@@ -163,7 +188,9 @@ abstract class Expression
/**
* @param $sConditionExpr
*
* @return Expression
* @throws \OQLException
*/
static public function FromOQL($sConditionExpr)
{
@@ -181,13 +208,14 @@ abstract class Expression
static public function FromSQL($sSQL)
{
$oSql = new SQLExpression($sSQL);
return $oSql;
return new SQLExpression($sSQL);
}
/**
* @param Expression $oExpr
*
* @return Expression
* @throws \CoreException
*/
public function LogAnd(Expression $oExpr)
{
@@ -198,7 +226,9 @@ abstract class Expression
/**
* @param Expression $oExpr
*
* @return Expression
* @throws \CoreException
*/
public function LogOr(Expression $oExpr)
{
@@ -222,6 +252,15 @@ abstract class Expression
return $sDefault;
}
/**
* @param $oSearch
* @param array $aArgs
* @param bool $bRetrofitParams
* @param \AttributeDefinition $oAttDef
*
* @return array
* @throws \MissingQueryArgument
*/
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
return array(
@@ -277,6 +316,12 @@ class SQLExpression extends Expression
return $this->m_sSQL;
}
// recursive rendering
public function toJSON(&$aArgs = null, $bRetrofitParams = false)
{
return null; // TODO should we throw an Exception ??
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -369,7 +414,7 @@ class BinaryExpression extends Expression
{
throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr)));
}
if ((($sOperator == "IN") || ($sOperator == "NOT IN")) && !($oRightExpr instanceof ListExpression))
if ((($sOperator == "IN") || ($sOperator == "NOT IN")) && !($oRightExpr instanceof ListExpression || $oRightExpr instanceof NestedQueryExpression))
{
throw new CoreException("Expecting a List Expression object on the right hand for operator $sOperator",
array('found_class' => get_class($oRightExpr)));
@@ -413,6 +458,32 @@ class BinaryExpression extends Expression
return "($sLeft $sOperator $sRight)";
}
/**
* {@inheritDoc}
* @throws \MissingQueryArgument
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
if (($this->GetOperator() == 'AND') || ($this->GetOperator() == 'OR'))
{
return array(
'type' => static::OPERATOR_BOOLEAN,
'operator' => $this->GetOperator(),
'left' => $this->GetLeftExpr()->ToJSON($aArgs, $bRetrofitParams),
'right' => $this->GetRightExpr()->ToJSON($aArgs, $bRetrofitParams),
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
return array(
'type' => static::OPERATOR_BINARY,
'operator' => $this->GetOperator(),
'left' => $this->GetLeftExpr()->ToJSON($aArgs, $bRetrofitParams),
'right' => $this->GetRightExpr()->ToJSON($aArgs, $bRetrofitParams),
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -420,6 +491,12 @@ class BinaryExpression extends Expression
$this->m_oRightExpr->Browse($callback);
}
/**
* @param $aArgs
*
* @throws \CoreException
* @throws \MissingQueryArgument
*/
public function ApplyParameters($aArgs)
{
if ($this->m_oLeftExpr instanceof VariableExpression)
@@ -446,13 +523,21 @@ class BinaryExpression extends Expression
$this->GetRightExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
}
/**
* @param array $aTranslationData
* @param bool $bMatchAll
* @param bool $bMarkFieldsAsResolved
*
* @return \BinaryExpression|\Expression
* @throws \CoreException
*/
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
$oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
$oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
return new BinaryExpression($oLeft, $this->GetOperator(), $oRight);
}
public function ListRequiredFields()
{
$aLeft = $this->GetLeftExpr()->ListRequiredFields();
@@ -521,6 +606,18 @@ class BinaryExpression extends Expression
}
// recursive rendering
/**
* @param \DBSearch $oSearch
* @param null $aArgs
* @param null $oAttDef
* @param array $aCtx
*
* @return array|string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
*/
public function Display($oSearch, &$aArgs = null, $oAttDef = null, &$aCtx = array())
{
$bReverseOperator = false;
@@ -601,6 +698,8 @@ class BinaryExpression extends Expression
* @param null $oAttDef
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
*/
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
@@ -712,7 +811,7 @@ class BinaryExpression extends Expression
/**
* @since 2.6 N°931 tag fields
* @since 2.6.0 N°931 tag fields
*/
class MatchExpression extends BinaryExpression
{
@@ -789,6 +888,20 @@ class UnaryExpression extends Expression
return CMDBSource::Quote($this->m_value);
}
/**
* {@inheritDoc}
* @throws \MissingQueryArgument
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return array(
'type' => static::OPERATOR_UNARY,
'value' => $this->m_value,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -835,6 +948,13 @@ class UnaryExpression extends Expression
class ScalarExpression extends UnaryExpression
{
/**
* ScalarExpression constructor.
*
* @param $value
*
* @throws \CoreException
*/
public function __construct($value)
{
if (!is_scalar($value) && !is_null($value) && (!$value instanceof OqlHexValue))
@@ -909,11 +1029,38 @@ class ScalarExpression extends UnaryExpression
return $sRet;
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return array(
'type' => static::OPERATOR_SCALAR,
'value' => $this->m_value,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
/**
* @param $aArgs
*
* @return \ScalarExpression
*/
public function GetAsScalar($aArgs)
{
return clone $this;
}
/**
* @param $oSearch
* @param array $aArgs
* @param bool $bRetrofitParams
* @param \AttributeDefinition $oAttDef
*
* @return array
* @throws \MissingQueryArgument
*/
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$aCriterion = array();
@@ -999,6 +1146,38 @@ class ScalarExpression extends UnaryExpression
IssueLog::Error($e->getMessage());
}
break;
case ($oAttDef instanceof AttributeEnumSet):
try
{
if (!empty($this->GetValue()))
{
$aValues = array();
$sValue = $this->GetValue();
if (is_string($sValue))
{
$aTags = $oAttDef->FromStringToArray($sValue, ' ');
}
else
{
$aTags = array();
}
foreach($aTags as $sLabel => $sValue)
{
$aValue['label'] = $sLabel;
$aValue['value'] = $sValue;
$aValues[] = $aValue;
}
$aCriterion['values'] = $aValues;
}
else
{
$aCriterion['has_undefined'] = true;
}
} catch (Exception $e)
{
IssueLog::Error($e->getMessage());
}
break;
case $oAttDef->IsExternalKey():
try
{
@@ -1122,6 +1301,7 @@ class FieldExpression extends UnaryExpression
* @return array|string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function Display($oSearch, &$aArgs = null, $oAttDef = null, &$aCtx = array())
{
@@ -1157,6 +1337,28 @@ class FieldExpression extends UnaryExpression
return "`{$this->m_sParent}`.`{$this->m_sName}`";
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return array(
'type' => static::OPERATOR_FIELD,
'value' => $this->m_value,
'alias' => $this->m_sParent,
'field' => $this->m_sName,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
/**
* @param array $aClasses
*
* @return \AttributeDefinition|\AttributeInteger|null
* @throws \CoreException
* @throws \Exception
*/
public function GetAttDef($aClasses = array())
{
if (!empty($this->m_sParent))
@@ -1213,6 +1415,14 @@ class FieldExpression extends UnaryExpression
}
}
/**
* @param array $aTranslationData
* @param bool $bMatchAll
* @param bool $bMarkFieldsAsResolved
*
* @return \Expression|\FieldExpression|\FieldExpressionResolved|mixed|\UnaryExpression
* @throws \CoreException
*/
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
if (!array_key_exists($this->m_sParent, $aTranslationData))
@@ -1256,6 +1466,7 @@ class FieldExpression extends UnaryExpression
*
* @return string label
* @throws \CoreException
* @throws \Exception
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
@@ -1267,7 +1478,7 @@ class FieldExpression extends UnaryExpression
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
// Set a default value for the general case
$sRes = $oAttDef->GetAsHtml($sValue);
$sRes = null;
// Exceptions...
if ($oAttDef->IsExternalKey())
@@ -1293,6 +1504,10 @@ class FieldExpression extends UnaryExpression
$sRes = Dict::S('UI:UndefinedObject');
}
}
if(is_null($sRes))
{
$sRes = $oAttDef->GetAsHtml($sValue);
}
return $sRes;
}
@@ -1331,6 +1546,7 @@ class FieldExpression extends UnaryExpression
* @param AttributeDefinition $oAttDef
*
* @return array
* @throws \CoreException
*/
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
@@ -1439,6 +1655,16 @@ class VariableExpression extends UnaryExpression
return $this->m_sName;
}
/**
* @param \DBSearch $oSearch
* @param null $aArgs
* @param null $oAttDef
* @param array $aCtx
*
* @return array|mixed|string
* @throws \MissingQueryArgument
* @throws \Exception
*/
public function Display($oSearch, &$aArgs = null, $oAttDef = null, &$aCtx = array())
{
$sValue = $this->m_value;
@@ -1486,7 +1712,8 @@ class VariableExpression extends UnaryExpression
$oObj = MetaModel::GetObject($sTarget, $sValue);
return $oObj->Get("friendlyname");
} catch (CoreException $e)
}
catch (CoreException $e)
{
}
}
@@ -1546,6 +1773,19 @@ class VariableExpression extends UnaryExpression
}
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return array(
'type' => static::OPERATOR_VARIABLE,
'value' => $this->m_value,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function RenameParam($sOldName, $sNewName)
{
if ($this->m_sName == $sOldName)
@@ -1554,6 +1794,14 @@ class VariableExpression extends UnaryExpression
}
}
/**
* @param $aArgs
*
* @return \ListExpression|\ScalarExpression|null
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \Exception
*/
public function GetAsScalar($aArgs)
{
$oRet = null;
@@ -1613,6 +1861,12 @@ class ListExpression extends Expression
$this->m_aExpressions = $aExpressions;
}
/**
* @param $aScalars
*
* @return \ListExpression
* @throws \CoreException
*/
public static function FromScalars($aScalars)
{
$aExpressions = array();
@@ -1635,6 +1889,14 @@ class ListExpression extends Expression
}
// recursive rendering
/**
* @param bool $bForSQL
* @param null $aArgs
* @param bool $bRetrofitParams
*
* @return array|string
*/
public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false)
{
$aRes = array();
@@ -1645,6 +1907,24 @@ class ListExpression extends Expression
return '('.implode(', ', $aRes).')';
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
$aFields = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aFields[] = $oExpr->ToJSON($aArgs, $bRetrofitParams);
}
return array(
'type' => static::OPERATOR_LIST,
'fields' => $aFields,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -1654,6 +1934,12 @@ class ListExpression extends Expression
}
}
/**
* @param $aArgs
*
* @throws \CoreException
* @throws \MissingQueryArgument
*/
public function ApplyParameters($aArgs)
{
foreach ($this->m_aExpressions as $idx => $oExpr)
@@ -1759,6 +2045,121 @@ class ListExpression extends Expression
}
}
class NestedQueryExpression extends Expression
{
/** @var DBSearch */
protected $m_oNestedQuery;
/*$m_oNestedQuery is an DBSearch object*/
public function __construct($oNestedQuery)
{
$this->m_oNestedQuery = $oNestedQuery;
}
/**
* @param OQLObjectQuery $oObjQuery
*
* @return \NestedQueryExpression
*/
public static function FromOQLObjectQuery($oObjQuery)
{
$oExpressions = $oObjQuery->ToDBSearch("");
return new NestedQueryExpression($oExpressions);
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetNestedQuery()
{
return $this->m_oNestedQuery;
}
// recursive rendering
/**
* @param bool $bForSQL
* @param null $aArgs
* @param bool $bRetrofitParams
*
* @return array|string
* @throws \CoreException
* @throws \MissingQueryArgument
*/
public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false)
{
if ($bForSQL)
{
$aAttToLoad = array();
foreach ($this->m_oNestedQuery->GetSelectedClasses() as $sClassAlias => $sClass)
{
$aAttToLoad[$sClassAlias] = array();
}
return '('.$this->m_oNestedQuery->MakeSelectQuery(array(), $aArgs, $aAttToLoad).')';
}
else
{
return '('.$this->m_oNestedQuery->ToOQL(false, null, false).')';
}
}
public function Browse(Closure $callback)
{
$callback($this);
}
/**/
public function ApplyParameters($aArgs)
{
$this->m_oNestedQuery->ApplyParameters($aArgs);
}
/**/
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
/**/
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
// Check and prepare the select information
$this->m_oNestedQuery->TranslateConditions($aTranslationData, $bMatchAll , $bMarkFieldsAsResolved );
return clone $this;
}
public function ListRequiredFields()
{
return array();
}
public function CollectUsedParents(&$aTable)
{
}
public function ListConstantFields()
{
return $this->m_oNestedQuery->ListConstantFields();
}
public function RenameParam($sOldName, $sNewName)
{
$this->m_oNestedQuery->RenameParam($sOldName, $sNewName);
}
public function RenameAlias($sOldName, $sNewName)
{
$this->m_oNestedQuery->RenameAlias($sOldName, $sNewName);
}
/**
* @inheritDoc
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return $this->m_oNestedQuery->ToJSON();
}
}
class FunctionExpression extends Expression
{
@@ -1798,6 +2199,26 @@ class FunctionExpression extends Expression
return $this->m_sVerb.'('.implode(', ', $aRes).')';
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
$aFields = array();
foreach ($this->m_aArgs as $oExpr)
{
$aFields[] = $oExpr->ToJSON($aArgs, $bRetrofitParams);
}
return array(
'type' => static::OPERATOR_FUNCTION,
'operator' => $this->m_sVerb,
'fields' => $aFields,
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -2093,6 +2514,20 @@ class IntervalExpression extends Expression
return 'INTERVAL '.$this->m_oValue->RenderExpression($bForSQL, $aArgs, $bRetrofitParams).' '.$this->m_sUnit;
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
return array(
'type' => static::OPERATOR_INTERVAL,
'unit' => $this->m_sUnit,
'expression' => $this->m_oValue->ToJSON($aArgs, $bRetrofitParams),
'oql' => $this->RenderExpression(false, $aArgs, $bRetrofitParams),
);
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -2192,6 +2627,25 @@ class CharConcatExpression extends Expression
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
*/
public function ToJSON(&$aArgs = null, $bRetrofitParams = false)
{
$aFields = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aFields[] = $oExpr->RenderExpression($aArgs, $bRetrofitParams);
}
return array(
'type' => static::OPERATOR_FUNCTION,
'operator' => 'concat',
'fields' => $aFields,
);
}
public function Browse(Closure $callback)
{
$callback($this);

View File

@@ -179,6 +179,7 @@ class OQLLexerRaw
'/\G(0x[0-9a-fA-F]+)/ ',
'/\G([0-9]+)/ ',
'/\G\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/ ',
'/\GNULL/ ',
'/\G([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ ',
'/\G:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/ ',
'/\G\\./ ',
@@ -637,14 +638,19 @@ class OQLLexerRaw
function yy_r1_72($yy_subpatterns)
{
$this->token = OQLParser::NAME;
$this->token = OQLParser::NULL_VAL;
}
function yy_r1_73($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
$this->token = OQLParser::NAME;
}
function yy_r1_74($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
}
function yy_r1_75($yy_subpatterns)
{
$this->token = OQLParser::DOT;

View File

@@ -148,6 +148,7 @@ above = "ABOVE"
above_strict = "ABOVE STRICT"
not_above = "NOT ABOVE"
not_above_strict = "NOT ABOVE STRICT"
null_val = "NULL"
//
// WARNING: there seems to be a bug in the Lexer about matching the longest pattern
// when there are alternates in the regexp.
@@ -391,6 +392,9 @@ numval {
strval {
$this->token = OQLParser::STRVAL;
}
null_val {
$this->token = OQLParser::NULL_VAL;
}
name {
$this->token = OQLParser::NAME;
}

File diff suppressed because it is too large Load Diff

View File

@@ -125,10 +125,16 @@ expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). {
expression_prio4(A) ::= expression_prio3(X). { A = X; }
expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); }
list(A) ::= PAR_OPEN list_items(X) PAR_CLOSE. {
A = new ListOqlExpression(X);
}
list(A) ::= PAR_OPEN query(X) PAR_CLOSE. {
A = new NestedQueryOqlExpression(X);
}
list(A) ::= PAR_OPEN union(X) PAR_CLOSE. {
A = new NestedQueryOqlExpression(X);
}
list_items(A) ::= expression_prio4(X). {
A = array(X);
}
@@ -159,9 +165,11 @@ interval_unit(A) ::= F_YEAR(X). { A = X; }
scalar(A) ::= num_scalar(X). { A = X; }
scalar(A) ::= str_scalar(X). { A = X; }
scalar(A) ::= null_scalar(X). { A = X; }
num_scalar(A) ::= num_value(X). { A = new ScalarOqlExpression(X); }
str_scalar(A) ::= str_value(X). { A = new ScalarOqlExpression(X); }
null_scalar(A) ::= NULL_VAL. { A = new ScalarOqlExpression(null); }
field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }

View File

@@ -188,6 +188,43 @@ class ScalarOqlExpression extends ScalarExpression implements CheckableExpressio
}
}
class NestedQueryOqlExpression extends NestedQueryExpression implements CheckableExpression
{
/** @var OQLObjectQuery */
private $m_oOQLObjectQuery;
/**
* NestedQueryOqlExpression constructor.
*
* @param OQLObjectQuery $oOQLObjectQuery
*/
public function __construct($oOQLObjectQuery )
{
parent::__construct($oOQLObjectQuery->ToDBSearch(""));
$this->m_oOQLObjectQuery = $oOQLObjectQuery;
}
/**
* Recursively check the validity of the expression with regard to the data model
* and the query in which it is used
*
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aAliases
* @param string $sSourceQuery
*
* @throws \OqlNormalizeException
*/
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
$this->m_oOQLObjectQuery->Check($oModelReflection, "", $aAliases);
}
public function GetOQLObjectQuery()
{
return $this->m_oOQLObjectQuery;
}
}
class FieldOqlExpression extends FieldExpression implements CheckableExpression
{
protected $m_oParent;
@@ -399,7 +436,7 @@ class OqlObjectQuery extends OqlQuery
* @param ModelReflection $oModelReflection MetaModel to consider
* @throws OqlNormalizeException
*/
public function Check(ModelReflection $oModelReflection, $sSourceQuery)
public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParentAliases = array())
{
$sClass = $this->GetClass($oModelReflection);
$sClassAlias = $this->GetClassAlias();
@@ -409,7 +446,7 @@ class OqlObjectQuery extends OqlQuery
throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
}
$aAliases = array($sClassAlias => $sClass);
$aAliases = array_merge(array($sClassAlias => $sClass),$aParentAliases);
$aJoinSpecs = $this->GetJoins();
if (is_array($aJoinSpecs))
@@ -567,6 +604,7 @@ class OqlUnionQuery extends OqlQuery
public function __construct(OqlObjectQuery $oLeftQuery, OqlQuery $oRightQueryOrUnion)
{
parent::__construct();
$this->aQueries[] = $oLeftQuery;
if ($oRightQueryOrUnion instanceof OqlUnionQuery)
{
@@ -637,6 +675,7 @@ class OqlUnionQuery extends OqlQuery
}
foreach ($aColumnToClasses as $iColumn => $aClasses)
{
$sRootClass = null;
foreach ($aClasses as $iQuery => $aData)
{
if ($iQuery == 0)
@@ -709,10 +748,6 @@ class OqlUnionQuery extends OqlQuery
// first loop
$sAncestor = $sClass;
}
elseif ($sClass == $sAncestor)
{
// remains the same
}
elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
{
$sAncestor = null;
@@ -764,4 +799,4 @@ class OqlUnionQuery extends OqlQuery
$oSearch = new DBUnionSearch($aSearches);
return $oSearch;
}
}
}

View File

@@ -1 +1 @@
2018-08-31
2019-12-03

View File

@@ -64,12 +64,26 @@ class OQLActualClassTreeResolver
$aTranslateFields = array();
foreach ($aExpectedAttributes as $sAttCode => $oExpression)
{
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
// 'id' is managed later
if ($sAttCode == 'id')
{
continue;
}
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
if (is_null($aClassAndAncestorsNodes[$sOriginClass]))
// Attributes can be stored in attributes list or for magic ones into filter codes list.
$sOriginClass = null;
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
}
else if (MetaModel::IsValidFilterCode($sClass, $sAttCode))
{
$sOriginClass = MetaModel::GetFilterCodeOrigin($sClass, $sAttCode);
}
else
{
continue;
}
if (!isset($aClassAndAncestorsNodes[$sOriginClass]) || is_null($aClassAndAncestorsNodes[$sOriginClass]))
{
if ($sOriginClass == $sClass)
{
@@ -198,4 +212,4 @@ class OQLActualClassTreeResolver
}
}
}
}
}

View File

@@ -128,6 +128,19 @@ class OQLClassNode
return $sOQL;
}
public function Browse(Closure $callback)
{
$callback($this);
foreach ($this->GetJoins() as $aJoins)
{
/** @var \OQLJoin $oJoin */
foreach ($aJoins as $oJoin)
{
$oJoin->GetOOQLClassNode()->Browse($callback);
}
}
}
public function GetExternalKeys()
{
return $this->aExtKeys;
@@ -318,4 +331,4 @@ class OQLJoin
return $this->sRightField;
}
}
}

View File

@@ -8,7 +8,7 @@
class OQLClassTreeBuilder
{
/** @var \DBObjectSearch */
private $oDBObjetSearch;
private $oDBObjectSearch;
/** @var OQLClassNode */
private $oOQLClassNode;
/** @var \QueryBuilderContext */
@@ -23,10 +23,10 @@ class OQLClassTreeBuilder
* @param \DBObjectSearch $oDBObjetSearch
* @param \QueryBuilderContext $oBuild
*/
public function __construct($oDBObjetSearch, $oBuild)
protected function __construct($oDBObjetSearch, $oBuild)
{
$this->oBuild = $oBuild;
$this->oDBObjetSearch = $oDBObjetSearch;
$this->oDBObjectSearch = $oDBObjetSearch;
$this->sClass = $oDBObjetSearch->GetFirstJoinedClass();
$this->sClassAlias = $oDBObjetSearch->GetFirstJoinedClassAlias();
if (empty($this->sClassAlias))
@@ -36,6 +36,25 @@ class OQLClassTreeBuilder
$this->oOQLClassNode = new OQLClassNode($oBuild, $this->sClass, $this->sClassAlias);
}
/**
* @param \DBObjectSearch $oDBObjetSearch
* @param \QueryBuilderContext $oBuild
*
* @return \OQLClassNode
* @throws \CoreException
*/
public static function GetOQLClassTree($oDBObjetSearch, $oBuild)
{
$oOQLClassTreeBuilder = new OQLClassTreeBuilder($oDBObjetSearch, $oBuild);
$oOQLClassNode = $oOQLClassTreeBuilder->DevelopOQLClassNode();
$oOQLClassTreeOptimizer = new OQLClassTreeOptimizer($oOQLClassNode, $oBuild);
$oOQLClassTreeOptimizer->OptimizeClassTree();
$oOQLActualClassTreeResolver = new OQLActualClassTreeResolver($oOQLClassNode, $oBuild);
$oOQLClassNode = $oOQLActualClassTreeResolver->Resolve();
return $oOQLClassNode;
}
/**
* Develop OQL.
* Add joins from OQL (outgoing and incoming)
@@ -50,6 +69,7 @@ class OQLClassTreeBuilder
*/
public function DevelopOQLClassNode()
{
$this->TranslateNestedRequests();
$this->AddExternalKeysFromSearch();
$aPolymorphicJoinAlias = $this->TranslatePolymorphicExpressions();
$this->AddExpectedExternalFields();
@@ -69,7 +89,7 @@ class OQLClassTreeBuilder
*/
private function AddExternalKeysFromSearch()
{
foreach ($this->oDBObjetSearch->GetCriteria_PointingTo() as $sKeyAttCode => $aPointingTo)
foreach ($this->oDBObjectSearch->GetCriteria_PointingTo() as $sKeyAttCode => $aPointingTo)
{
if (array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo))
{
@@ -202,7 +222,7 @@ class OQLClassTreeBuilder
private function JoinClassesForExternalKeys()
{
// Get filters from the search outgoing joins
$aAllPointingTo = $this->oDBObjetSearch->GetCriteria_PointingTo();
$aAllPointingTo = $this->oDBObjectSearch->GetCriteria_PointingTo();
// Add filters from external keys
foreach (array_keys($this->oOQLClassNode->GetExternalKeys()) as $sKeyAttCode)
@@ -316,7 +336,7 @@ class OQLClassTreeBuilder
*/
private function JoinClassesReferencedBy()
{
foreach ($this->oDBObjetSearch->GetCriteria_ReferencedBy() as $sForeignClass => $aReferences)
foreach ($this->oDBObjectSearch->GetCriteria_ReferencedBy() as $sForeignClass => $aReferences)
{
foreach ($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
@@ -375,4 +395,13 @@ class OQLClassTreeBuilder
$this->oOQLClassNode->AddLeftJoin($oSelectPoly, 'id', 'id', true);
}
}
}
/**
* Rename class aliases of nested requests to avoid collision with main request
*/
private function TranslateNestedRequests()
{
$aClassAliases = $this->oDBObjectSearch->GetJoinedClasses();
$this->oDBObjectSearch->RenameNestedQueriesAliasesInNameSpace($aClassAliases);
}
}

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