Compare commits

...

194 Commits

Author SHA1 Message Date
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
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
2370 changed files with 9452 additions and 154229 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

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

@@ -71,6 +71,7 @@
<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" />

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

@@ -34,7 +34,13 @@ do
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

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:

View File

@@ -52,16 +52,16 @@ iTop also offers mass import tools and web services to integrate with your IT
## Last releases
### Versions 2.7.*
- 2.7.0-beta published on December 18, 2019
- 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-beta][65]
- [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-beta
[65]: https://sourceforge.net/projects/itop/files/itop/2.7.0-beta2
### Versions 2.6.*
@@ -69,12 +69,12 @@ iTop also offers mass import tools and web services to integrate with your IT
- [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.*
@@ -88,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

View File

@@ -1,20 +1,22 @@
<?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;
@@ -30,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
{
@@ -41,6 +44,9 @@ interface iLoginExtension
public function ListSupportedLoginModes();
}
/**
* @since 2.7.0
*/
interface iLoginFSMExtension extends iLoginExtension
{
/**
@@ -58,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();
/**
@@ -151,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
{
/**
@@ -180,6 +215,9 @@ interface iLogoutExtension extends iLoginExtension
public function LogoutAction();
}
/**
* @since 2.7.0
*/
interface iLoginUIExtension extends iLoginExtension
{
/**
@@ -188,7 +226,11 @@ interface iLoginUIExtension extends iLoginExtension
public function GetTwigContext();
}
/**
* @api
* @package Extensibility
* @since 2.7.0
*/
interface iPreferencesExtension
{
/**
@@ -206,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.
*
@@ -366,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
*
@@ -393,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.
@@ -421,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 $oObject::ListChangesUpdated()}. Do not call {@link \DBObject::ListChanges} for this purpose because it will be empty as the object has already be written to DB!
* 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
@@ -429,7 +572,7 @@ interface iApplicationObjectExtension
*
* @return void
*
* @since 2.7.0 N°2293 can access object changes by calling {@link $oObject::ListChangesUpdated()}
* @since 2.7.0 N°2293 can access object changes by calling {@see DBObject::ListPreviousValuesForUpdatedAttributes()} on $oObject
*/
public function OnDBUpdate($oObject, $oChange = null);
@@ -460,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.
@@ -586,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)
{
@@ -848,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
*
@@ -855,7 +1088,7 @@ interface iPageUIExtension
*
* @api
* @package Extensibility
* @since 2.4
* @since 2.4.0
*/
interface iPortalUIExtension
{
@@ -1079,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()
{

View File

@@ -150,7 +150,7 @@ EOF
* @param bool $bMustNotExist
*
* @see SetSessionMessage()
* @since 2.6
* @since 2.6.0
*/
protected function SetSessionMessageFromInstance($sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
@@ -495,7 +495,10 @@ EOF
$bCanEdit = UserRights::IsAdministrator() || $oAttDef->IsUserEditable();
$sDivId = $oDashboard->GetId();
$oPage->add('<div class="dashboard_contents" id="'.$sDivId.'">');
$aExtraParams = array('query_params' => $this->ToArgsForQuery());
$aExtraParams = array(
'query_params' => $this->ToArgsForQuery(),
'dashboard_div_id' => $sDivId,
);
$oDashboard->Render($oPage, false, $aExtraParams, $bCanEdit);
$oPage->add('</div>');
}
@@ -943,6 +946,7 @@ EOF
$val['attcode'] = $sAttCode;
$val['atttype'] = $sAttDefClass;
$val['attlabel'] = $sAttLabel;
$val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY;
// - How the field should be rendered
$val['layout'] = (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? 'large' : 'small';
@@ -3217,7 +3221,6 @@ EOF
$data = $oDoc->GetData();
switch ($oDoc->GetMimeType())
{
case 'text/html':
case 'text/xml':
$oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n");
break;
@@ -4022,7 +4025,7 @@ EOF
/**
* @param string $sMessageIdPrefix
*
* @since 2.6
* @since 2.6.0
*/
protected function SetWarningsAsSessionMessages($sMessageIdPrefix)
{
@@ -4203,6 +4206,7 @@ EOF
{
$oPage->SetCurrentTab('UI:PropertiesTab');
$sClass = get_class($this);
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
@@ -4211,6 +4215,7 @@ EOF
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
if ($iFlags & OPT_ATT_HIDDEN)
{
// The case log is hidden do nothing
@@ -4218,6 +4223,16 @@ EOF
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$sAttDefClass = get_class($oAttDef);
$sAttLabel = $oAttDef->GetLabel();
$sAttMetaDataLabel = utils::HtmlEntities($sAttLabel);
$sAttMetaDataFlagHidden = (($iFlags & OPT_ATT_HIDDEN) === OPT_ATT_HIDDEN) ? 'true' : 'false';
$sAttMetaDataFlagReadOnly = (($iFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY) ? 'true' : 'false';
$sAttMetaDataFlagMandatory = (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY) ? 'true' : 'false';
$sAttMetaDataFlagMustChange = (($iFlags & OPT_ATT_MUSTCHANGE) === OPT_ATT_MUSTCHANGE) ? 'true' : 'false';
$sAttMetaDataFlagMustPrompt = (($iFlags & OPT_ATT_MUSTPROMPT) === OPT_ATT_MUSTPROMPT) ? 'true' : 'false';
$sAttMetaDataFlagSlave = (($iFlags & OPT_ATT_SLAVE) === OPT_ATT_SLAVE) ? 'true' : 'false';
$sInputId = $this->m_iFormId.'_'.$sAttCode;
if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)))
@@ -4254,19 +4269,32 @@ EOF
$sValue = $this->Get($sAttCode);
$sDisplayValue = $this->GetEditValue($sAttCode);
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
$sHTMLValue = '';
if ($sComment != '')
{
$sHTMLValue = '<span>'.$sComment.'</span><br/>';
}
$sHTMLValue .= "<span style=\"font-family:Tahoma,Verdana,Arial,Helvetica;font-size:12px;\" id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage,
$sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags,
$aArgs).'</span>';
$sCommentAsHtml = ($sComment != '') ? '<span>'.$sComment.'</span><br/>' : '';
$sFieldAsHtml = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs);
$sHTMLValue = <<<HTML
<div class="field_data">
<div class="field_value">
$sCommentAsHtml
$sFieldAsHtml
</div>
</div>
HTML;
$aFieldsMap[$sAttCode] = $sInputId;
}
$oPage->add('<fieldset><legend>'.$oAttDef->GetLabel().'</legend>');
$oPage->add($sHTMLValue);
$oPage->add('</fieldset>');
$oPage->add(<<<HTML
<fieldset>
<legend>{$sAttLabel}</legend>
<div class="field_container field_large" data-attribute-code="{$sAttCode}" data-attribute-type="{$sAttDefClass}" data-attribute-label="{$sAttMetaDataLabel}"
data-attribute-flag-hidden="{$sAttMetaDataFlagHidden}" data-attribute-flag-read-only="{$sAttMetaDataFlagReadOnly}" data-attribute-flag-mandatory="{$sAttMetaDataFlagMandatory}"
data-attribute-flag-must-change="{$sAttMetaDataFlagMustChange}" data-attribute-flag-must-prompt="{$sAttMetaDataFlagMustPrompt}" data-attribute-flag-slave="{$sAttMetaDataFlagSlave}">
{$sHTMLValue}
</div>
</fieldset>
HTML
);
}
}

View File

@@ -176,12 +176,6 @@ abstract class Dashboard
protected function InitDashletFromDOMNode($oDomNode)
{
$sId = $oDomNode->getAttribute('id');
// To avoid collision with other dashlets with the same ID we suffix it. Collisions typically happen with extensions.
// Note: The check is done so we don't append it at each save of the dashboard.
if(strpos($sId, 'uniqid_') === false)
{
$sId .= '_uniqid_' . uniqid();
}
$sDashletType = $oDomNode->getAttribute('xsi:type');
@@ -341,6 +335,25 @@ 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
*/
@@ -516,10 +529,25 @@ EOF
*/
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)
{
@@ -561,19 +589,21 @@ EOF
// 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>');
}
}
@@ -633,6 +663,18 @@ 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 \DesignerForm $oForm
* @param array $aExtraParams
@@ -657,11 +699,34 @@ 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;
}
}
@@ -670,12 +735,12 @@ EOF
*/
class RuntimeDashboard extends Dashboard
{
/** @var bool $bCustomized */
protected $bCustomized;
/** @var string $sDefinitionFile */
private $sDefinitionFile = '';
/** @var null $sReloadURL */
private $sReloadURL = null;
/** @var bool $bCustomized */
protected $bCustomized;
/**
* @inheritDoc
@@ -683,12 +748,22 @@ class RuntimeDashboard extends Dashboard
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)
{
@@ -846,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());
@@ -909,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">';
@@ -1098,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">');
@@ -1129,7 +1206,7 @@ EOF
$sAutoApplyConfirmationMessage = addslashes(Dict::S('UI:AutoApplyConfirmationMessage'));
$oPage->add_ready_script(
<<<EOF
<<<JS
window.bLeavingOnUserAction = false;
$('#dashboard_editor').dialog({
@@ -1172,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'}
});
@@ -1214,7 +1296,7 @@ window.onbeforeunload = function() {
}
// return nothing ! safer for IE
};
EOF
JS
);
$oPage->add_ready_script("");
}
@@ -1354,7 +1436,7 @@ EOF
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$oPage->add_ready_script(
<<<EOF
<<<JS
$('#dashlet_creation_dlg').dialog({
width: 600,
modal: true,
@@ -1383,7 +1465,7 @@ $('#dashlet_creation_dlg').dialog({
],
close: function() { $(this).remove(); }
});
EOF
JS
);
}
@@ -1418,4 +1500,83 @@ EOF
{
$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,7 +15,7 @@
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
<rank>80</rank>
</menu>
<menu id="System" xsi:type="MenuGroup" _delta="define">
<menu id="SystemTools" xsi:type="MenuGroup" _delta="define">
<rank>100</rank>
<enable_class>ResourceSystemMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>

View File

@@ -1158,9 +1158,18 @@ class DataTableSettings implements Serializable
*/
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;
}

View File

@@ -80,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');
@@ -93,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');
@@ -350,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
@@ -1645,7 +1661,7 @@ EOF;
* @param string $sCssClasses CSS classes to add to the container
*
* @throws \Exception
* @since 2.6
* @since 2.6.0
*/
public function AddHeaderMessage($sContent, $sCssClasses = 'message_info')
{

View File

@@ -1,12 +1,15 @@
<?php
/**
* Class LoginForm
*
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Class LoginForm
*
* @since 2.7.0
*/
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
{
private $bForceFormOnError = false;
@@ -21,6 +24,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
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 iLoginUIExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -66,6 +75,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -85,6 +97,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -94,6 +109,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
/**
* @inheritDoc
*/
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
@@ -105,7 +123,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
}
/**
* @return LoginTwigContext
* @inheritDoc
* @throws \Exception
*/
public function GetTwigContext()
@@ -125,7 +143,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$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,

View File

@@ -316,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();

View File

@@ -263,12 +263,15 @@ EOF
/**
* Handles the display of the sub-menus (called recursively if necessary)
*
* @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)
{
@@ -301,7 +304,7 @@ EOF
$sLinkTarget .= ' target="_blank"';
}
$sURL = '"'.$oMenu->GetHyperlink($aExtraParams).'"'.$sLinkTarget;
$sTitle = $oMenu->GetTitle();
$sTitle = utils::HtmlEntities($oMenu->GetTitle());
$sItemHtml .= "<a href={$sURL}>{$sTitle}</a>";
}
else
@@ -692,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)
@@ -737,8 +740,7 @@ class MenuGroup extends MenuNode
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -776,8 +778,7 @@ class TemplateMenuNode extends MenuNode
}
/**
* @param $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -786,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())
{
@@ -878,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())
{
@@ -902,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
@@ -927,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);
@@ -979,11 +974,9 @@ 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())
{
@@ -1039,8 +1032,7 @@ class WebPageMenuNode extends MenuNode
}
/**
* @param array $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1048,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())
{
@@ -1096,10 +1090,7 @@ class NewObjectMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
*
* @return string
* @throws \Exception
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1133,8 +1124,7 @@ class NewObjectMenuNode extends MenuNode
}
/**
* @param WebPage $oPage
* @param string[] $aExtraParams
* @inheritDoc
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
@@ -1172,8 +1162,7 @@ class DashboardMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1192,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())
{
@@ -1203,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>');
@@ -1289,8 +1277,7 @@ class DashboardMenuNode extends MenuNode
class ShortcutContainerMenuNode extends MenuNode
{
/**
* @param string[] $aExtraParams
* @return string
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1298,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
*/
@@ -1361,9 +1347,7 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @param string[] $aExtraParams
* @return string
* @throws CoreException
* @inheritDoc
*/
public function GetHyperlink($aExtraParams)
{
@@ -1381,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())
{
@@ -1393,8 +1375,9 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @return string
* @throws CoreException
* @inheritDoc
*
* @throws \Exception
*/
public function GetTitle()
{
@@ -1402,8 +1385,9 @@ class ShortcutMenuNode extends MenuNode
}
/**
* @return string
* @throws CoreException
* @inheritDoc
*
* @throws \Exception
*/
public function GetLabel()
{

View File

@@ -256,7 +256,7 @@ EOF
*/
protected function LoadTheme()
{
$sCssThemeUrl = ThemeHandler::GetDefaultThemeUrl();
$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

@@ -17,8 +17,6 @@
* You should have received a copy of the GNU Affero General Public License
*/
use ScssPhp\ScssPhp\Compiler;
/**
* Class ThemeHandler
*
@@ -28,15 +26,90 @@ use ScssPhp\ScssPhp\Compiler;
class ThemeHandler
{
/**
* Return the absolute URL for the default theme CSS file
* 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 GetDefaultThemeUrl()
public static function GetCurrentThemeUrl()
{
$sThemeId = MetaModel::GetConfig()->Get('backoffice_default_theme');
static::CompileTheme($sThemeId);
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';
@@ -72,12 +145,12 @@ class ThemeHandler
$sThemeFolderPath = $sWorkingPath.'/branding/themes/'.$sThemeId.'/';
$sThemeCssPath = $sThemeFolderPath.'main.css';
// Save parameters if passed...
// 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
// ... 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);

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

@@ -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;
@@ -1505,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');
}
/**

View File

@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
/**
* Generic interface common to CLI and Web pages
*/
@@ -120,6 +122,7 @@ class WebPage implements Page
protected $a_OutputOptions;
protected $bPrintable;
protected $bHasCollapsibleSection;
protected $bAddJSDict;
/**
* WebPage constructor.
@@ -150,6 +153,7 @@ class WebPage implements Page
$this->a_OutputOptions = array();
$this->bHasCollapsibleSection = false;
$this->bPrintable = $bPrintable;
$this->bAddJSDict = true;
ob_start(); // Start capturing the output
}
@@ -183,6 +187,21 @@ class WebPage implements Page
$this->s_content .= $s_html;
}
/**
* Add any rendered text or HTML fragment to the body of the page using a twig template
*
* @param string $sViewPath Absolute path of the templates folder
* @param string $sTemplateName Name of the twig template, ie MyTemplate for MyTemplate.html.twig
* @param array $aParams Params used by the twig template
* @param string $sDefaultType default type of the template ('html', 'xml', ...)
*
* @throws \Exception
*/
public function add_twig_template($sViewPath, $sTemplateName, $aParams = array(), $sDefaultType = 'html')
{
TwigHelper::RenderIntoPage($this, $sViewPath, $sTemplateName, $aParams, $sDefaultType);
}
/**
* 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
@@ -528,16 +547,37 @@ class WebPage implements Page
*/
public function GetDetails($aFields)
{
$aPossibleAttFlags = MetaModel::EnumPossibleAttributeFlags();
$sHtml = "<div class=\"details\">\n";
foreach ($aFields as $aAttrib)
{
$sLayout = isset($aAttrib['layout']) ? $aAttrib['layout'] : 'small';
// Prepare metadata attributes
$sDataAttributeCode = isset($aAttrib['attcode']) ? 'data-attribute-code="'.$aAttrib['attcode'].'"' : '';
$sDataAttributeType = isset($aAttrib['atttype']) ? 'data-attribute-type="'.$aAttrib['atttype'].'"' : '';
$sDataAttributeLabel = isset($aAttrib['attlabel']) ? 'data-attribute-label="'.utils::HtmlEntities($aAttrib['attlabel']).'"' : '';
// - Attribute flags
$sDataAttributeFlags = '';
if(isset($aAttrib['attflags']))
{
foreach($aPossibleAttFlags as $sFlagCode => $iFlagValue)
{
// Note: Skip normal flag as we don't need it.
if($sFlagCode === 'normal')
{
continue;
}
$sFormattedFlagCode = str_ireplace('_', '-', $sFlagCode);
$sFormattedFlagValue = (($aAttrib['attflags'] & $iFlagValue) === $iFlagValue) ? 'true' : 'false';
$sDataAttributeFlags .= 'data-attribute-flag-'.$sFormattedFlagCode.'="'.$sFormattedFlagValue.'" ';
}
}
// - Value raw
$sDataValueRaw = isset($aAttrib['value_raw']) ? 'data-value-raw="'.utils::HtmlEntities($aAttrib['value_raw']).'"' : '';
$sHtml .= "<div class=\"field_container field_{$sLayout}\" $sDataAttributeCode $sDataAttributeType $sDataAttributeLabel $sDataValueRaw>\n";
$sHtml .= "<div class=\"field_container field_{$sLayout}\" $sDataAttributeCode $sDataAttributeType $sDataAttributeLabel $sDataAttributeFlags $sDataValueRaw>\n";
$sHtml .= "<div class=\"field_label label\">{$aAttrib['label']}</div>\n";
$sHtml .= "<div class=\"field_data\">\n";
@@ -736,7 +776,10 @@ class WebPage implements Page
}
// Dict entries for JS
$this->output_dict_entries();
if ($this->bAddJSDict)
{
$this->output_dict_entries();
}
// JS files
foreach ($this->a_linked_scripts as $s_script)

View File

@@ -10,7 +10,7 @@
"ext-json": "*",
"ext-mysqli": "*",
"ext-soap": "*",
"combodo/tcpdf": "6.3.2",
"combodo/tcpdf": "6.3.4",
"nikic/php-parser": "^3.1",
"pear/archive_tar": "1.4.9",
"pelago/emogrifier": "2.1.0",
@@ -50,7 +50,8 @@
"classmap": [
"core",
"application",
"sources/application"
"sources/application",
"sources/Composer"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",
@@ -74,5 +75,10 @@
"allow-contrib": false,
"require": "3.4.*"
}
},
"scripts": {
"post-install-cmd": ["@rmDeniedTestDir"],
"post-update-cmd": ["@rmDeniedTestDir"],
"rmDeniedTestDir": "@php .make/composer/rmDeniedTestDir.php"
}
}

19
composer.lock generated
View File

@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3e413c47265b246174add07c2c91b5e9",
"content-hash": "b29eb2767d269b9ec2cf4d148dc083bc",
"packages": [
{
"name": "combodo/tcpdf",
"version": "6.3.2",
"version": "6.3.4",
"source": {
"type": "git",
"url": "https://github.com/combodo-itop-libs/TCPDF.git",
"reference": "2723050de47c8cbd78293656d896c0000442e23a"
"reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/2723050de47c8cbd78293656d896c0000442e23a",
"reference": "2723050de47c8cbd78293656d896c0000442e23a",
"url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d",
"reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d",
"shasum": ""
},
"require": {
@@ -49,9 +49,14 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
"LGPL-3.0-only"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
"role": "lead"
},
{
"name": "Combodo",
"email": "contact@combodo.com"
@@ -59,7 +64,7 @@
],
"description": "TCPDF fork adding requirements for iTop: Specific fonts.",
"homepage": "https://github.com/combodo-itop-libs/TCPDF",
"time": "2020-01-08T16:22:40+00:00"
"time": "2020-02-12T14:16:56+00:00"
},
{
"name": "nikic/php-parser",

View File

@@ -1033,6 +1033,20 @@ abstract class AttributeDefinition
$oFormField->AddMetadata('attribute-code', $this->GetCode());
$oFormField->AddMetadata('attribute-type', get_class($this));
$oFormField->AddMetadata('attribute-label', utils::HtmlEntities($this->GetLabel()));
// - Attribute flags
$aPossibleAttFlags = MetaModel::EnumPossibleAttributeFlags();
foreach($aPossibleAttFlags as $sFlagCode => $iFlagValue)
{
// Note: Skip normal flag as we don't need it.
if($sFlagCode === 'normal')
{
continue;
}
$sFormattedFlagCode = str_ireplace('_', '-', $sFlagCode);
$sFormattedFlagValue = (($iFlags & $iFlagValue) === $iFlagValue) ? 'true' : 'false';
$oFormField->AddMetadata('attribute-flag-'.$sFormattedFlagCode, $sFormattedFlagValue);
}
// - Value raw
if ($this::IsScalar())
{
$oFormField->AddMetadata('value-raw', utils::HtmlEntities($oObject->Get($this->GetCode())));
@@ -3726,7 +3740,7 @@ class AttributeFinalClass extends AttributeString
/**
* @return bool
* @since 2.7
* @since 2.7.0 N°2272 OQL perf finalclass in all intermediary tables
*/
public function CopyOnAllTables()
{
@@ -4380,7 +4394,7 @@ class AttributeLongText extends AttributeText
{
// Is there a way to know the current limitation for mysql?
// See mysql_field_len()
return 65535 * 1024; // Limited... still 64 Mb!
return 65535 * 1024; // Limited... still 64 MB!
}
}
@@ -6884,7 +6898,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey
if ($oFilter)
{
$oValSetDef = $this->GetValuesDef();
$oValSetDef->AddCondition($oFilter);
$oValSetDef->SetCondition($oFilter);
return $oValSetDef->GetValues($aArgs, $sContains);
}
@@ -6900,7 +6914,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey
$oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue);
if ($oFilter)
{
$oValSetDef->AddCondition($oFilter);
$oValSetDef->SetCondition($oFilter);
}
$oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue);
@@ -9705,21 +9719,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'max_items'));
}
/**
* Allowed values are mandatory for this attribute to be modified
*
* @param array $aArgs
* @param string $sContains
*
* @return array|null
* @throws \CoreException
* @throws \OQLException
*/
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
return parent::GetAllowedValues($aArgs, $sContains);
}
/**
* Allowed different values for the set values are mandatory for this attribute to be modified
*
@@ -9852,16 +9851,16 @@ abstract class AttributeSet extends AttributeDBFieldVoid
return 255;
}
public function FromStringToArray($proposedValue)
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue))
{
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
// convert also , separated strings
if ($sSepItem !== ',')
if ($sSepItem !== $sDefaultSepItem)
{
$proposedValue = str_replace(',', $sSepItem, $proposedValue);
$proposedValue = str_replace($sDefaultSepItem, $sSepItem, $proposedValue);
}
foreach(explode($sSepItem, $proposedValue) as $sCode)
{
@@ -9904,10 +9903,18 @@ abstract class AttributeSet extends AttributeDBFieldVoid
public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false)
{
$oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$aAllowedValues = $this->GetPossibleValues();
if (is_string($proposedValue) && !empty($proposedValue))
{
$proposedValue = trim("$proposedValue");
$aValues = $this->FromStringToArray($proposedValue);
foreach ($aValues as $i => $sValue)
{
if (!isset($aAllowedValues[$sValue]))
{
unset($aValues[$i]);
}
}
$oSet->SetValues($aValues);
}
elseif ($proposedValue instanceof ormSet)
@@ -10109,10 +10116,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
$aValues = $value->GetValues();
}
$sRes = implode($sSepItem, $aValues);
if (!empty($sRes))
{
$sRes = "{$sSepItem}{$sRes}{$sSepItem}";
}
}
else
{
@@ -10136,14 +10139,32 @@ abstract class AttributeSet extends AttributeDBFieldVoid
class AttributeEnumSet extends AttributeSet
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_TAG_SET;
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('possible_values', 'is_null_allowed', 'max_items'));
}
public function GetMaxSize()
{
$aRawValues = $this->GetRawPossibleValues();
$iMaxItems = $this->GetMaxItems();
$aLengths = array();
foreach (array_keys($aRawValues) as $sKey)
{
$aLengths[] = strlen($sKey);
}
rsort($aLengths, SORT_NUMERIC);
$iMaxSize = 2;
for ($i = 0; $i < min($iMaxItems, count($aLengths)); $i++)
{
$iMaxSize += $aLengths[$i] + 1;
}
return max(255, $iMaxSize);
}
private function GetRawPossibleValues($aArgs = array(), $sContains = '')
{
/** @var ValueSetEnumPadded $oValSetDef */
$oValSetDef = $this->Get('possible_values');
if (!$oValSetDef)
{
@@ -10167,8 +10188,13 @@ class AttributeEnumSet extends AttributeSet
public function GetValueLabel($sValue)
{
if ($sValue instanceof ormSet)
{
$sValue = implode(', ', $sValue->GetValues());
}
$aValues = $this->GetRawPossibleValues();
if (isset($aValues[$sValue]))
if (is_array($aValues) && is_string($sValue) && isset($aValues[$sValue]))
{
$sValue = $aValues[$sValue];
}
@@ -10184,9 +10210,14 @@ class AttributeEnumSet extends AttributeSet
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/);
if (is_null($sLabel))
{
$sDefault = str_replace('_', ' ', $sValue);
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false);
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, false);
if (is_null($sLabel))
{
$sDefault = trim(str_replace('_', ' ', $sValue));
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sDefault, $sDefault, false);
}
}
}
@@ -10245,6 +10276,122 @@ class AttributeEnumSet extends AttributeSet
return $sRes;
}
/**
* @param ormSet $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param \DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return mixed|string
* @throws \Exception
*/
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
if (is_object($value) && ($value instanceof ormSet))
{
$aValues = $value->GetValues();
if ($bLocalize)
{
$aLocalizedValues = array();
foreach($aValues as $sValue)
{
$aLocalizedValues[] = $this->GetValueLabel($sValue);
}
$aValues = $aLocalizedValues;
}
$sRes = implode($sSepItem, $aValues);
}
else
{
$sRes = '';
}
return "{$sTextQualifier}{$sRes}{$sTextQualifier}";
}
/**
* Get the value from a given string (plain text, CSV import)
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed null if no match could be found
* @throws \Exception
*/
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
{
if ($bLocalizedValue)
{
// Lookup for the values matching the input
//
$aValues = $this->FromStringToArray($sProposedValue);
$aFoundValues = array();
$aRawValues = $this->GetPossibleValues();
foreach ($aValues as $sValue)
{
$bFound = false;
foreach ($aRawValues as $sCode => $sRawValue)
{
if ($sValue == $sRawValue)
{
$aFoundValues[] = $sCode;
$bFound = true;
break;
}
}
if (!$bFound)
{
// Not found, break the import
return null;
}
}
return $this->MakeRealValue(implode(',', $aFoundValues), null);
}
else
{
return $this->MakeRealValue($sProposedValue, null, false);
}
}
/**
* @param string $proposedValue Search string used for MATCHES
*
* @param string $sDefaultSepItem word separator to extract items
*
* @return array of EnumSet codes
* @throws \Exception
*/
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue))
{
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
// convert also other separators
if ($sSepItem !== $sDefaultSepItem)
{
$proposedValue = str_replace($sDefaultSepItem, $sSepItem, $proposedValue);
}
foreach(explode($sSepItem, $proposedValue) as $sCode)
{
$sValue = trim($sCode);
if (strlen($sValue) > 2)
{
$sLabel = $this->GetValueLabel($sValue);
$aValues[$sLabel] = $sValue;
}
}
}
return $aValues;
}
}
@@ -10687,7 +10834,7 @@ class AttributeQueryAttCodeSet extends AttributeSet
* Multi value list of tags
*
* @see TagSetFieldData
* @since 2.6 N°931 tag fields
* @since 2.6.0 N°931 tag fields
*/
class AttributeTagSet extends AttributeSet
{
@@ -10766,7 +10913,7 @@ class AttributeTagSet extends AttributeSet
return json_encode($aJson);
}
public function FromStringToArray($proposedValue)
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue))

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;
@@ -1390,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)
@@ -1540,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

@@ -90,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 !
@@ -352,7 +361,7 @@ 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
@@ -396,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,
@@ -931,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)',
@@ -1250,10 +1250,8 @@ class Config
'show_in_conf_sample' => false,
),
);
/**
* @var \iTopConfigParser|null
*/
private $oItopConfigParser;
public function IsProperty($sPropCode)
{
@@ -1283,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', $bCanOverride = false)
{
$sType = $this->m_aSettings[$sPropCode]['type'];
$value = $this->oConfigPlaceholdersResolver->Resolve($value);
switch ($sType)
{
case 'bool':
@@ -1308,6 +1310,13 @@ 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;
@@ -1404,6 +1413,8 @@ class Config
*/
public function __construct($sConfigFile = null, $bLoadConfig = true)
{
$this->oConfigPlaceholdersResolver = new ConfigPlaceholdersResolver();
$this->m_sFile = $sConfigFile;
if (is_null($sConfigFile))
{
@@ -2001,16 +2012,7 @@ class Config
if (isset($aSettingInfo['default']))
{
if (isset($this->m_aCanOverrideSettings[$sPropCode]) && $this->m_aCanOverrideSettings[$sPropCode])
{
$aParserValue = $this->oItopConfigParser->GetVarValue('MySettings', $sPropCode);
}
else
{
$aParserValue = array('found' => false);
}
$sComment = self::PrettyVarExport($aParserValue,$aSettingInfo['default'], "\t//\t\t", true);
$sComment = self::PrettyVarExport(null,$aSettingInfo['default'], "\t//\t\t", true);
fwrite($hFile,"\t//\tdefault: {$sComment}\n");
}
@@ -2020,7 +2022,7 @@ class Config
}
else
{
$aParserValue = array('found' => false);
$aParserValue = null;
}
$sSeenAs = self::PrettyVarExport($aParserValue,$aSettingInfo['value'], "\t");
fwrite($hFile, "\t'$sPropCode' => $sSeenAs,\n");
@@ -2069,7 +2071,6 @@ class Config
$bReturn = fclose($hFile);
utils::SetConfig($this);
FileLog::RenameLegacyLogFiles();
return $bReturn;
}
@@ -2266,7 +2267,7 @@ class Config
*/
protected static function PrettyVarExport($aParserValue, $value, $sIndentation, $bForceIndentation = false)
{
if ($aParserValue['found'])
if (is_array($aParserValue) && $aParserValue['found'])
{
return $aParserValue['value'];
}
@@ -2290,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

@@ -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
{

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
*
@@ -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()
@@ -2036,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)
{
@@ -2088,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)
@@ -2237,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
*
@@ -2376,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()
@@ -2390,27 +2390,32 @@ abstract class DBObject implements iDisplay
{
return $this->ListChangedValues($this->m_aCurrValues);
}
else
{
return $this->m_aCurrValues;
}
return $this->m_aCurrValues;
}
/**
* List the changed attributes that were persisted by an update.
*
* @see \DBObject::ListChanges() use DBObject::ListChanges() if your code is BEFORE the update
*
* @return array
* @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 ListChangesUpdated()
public function ListPreviousValuesForUpdatedAttributes()
{
if (empty($this->m_aChanges))
if (empty($this->m_aPreviousValuesForUpdatedAttributes))
{
return array();
}
return $this->m_aChanges;
return $this->m_aPreviousValuesForUpdatedAttributes;
}
@@ -2660,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
@@ -2945,8 +2950,8 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
* @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.
* @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
*
@@ -2962,8 +2967,8 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
* @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.
* @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
*
@@ -3025,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()
{
@@ -3048,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();
@@ -3201,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();
@@ -3305,12 +3314,36 @@ 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 {@link DBUpdate} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
* @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
*
@@ -3581,8 +3614,8 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
* @deprecated 2.7.0 N°2361 simply use {@link DBDelete} instead.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
* @deprecated 2.7.0 N°2361 simply use {@see DBObject::DBDelete()} instead.
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*
* @param CMDBChange $oChange
* @param boolean $bSkipStrongSecurity
@@ -4135,13 +4168,15 @@ abstract class DBObject implements iDisplay
}
/**
* This method is called after the object is updated into DB. You can get changes using @link m_aChanges}.
*
* Warning : do not use {@link ListChanges} as it will return an empty array.
*
* @overwritable-hook You can extend this method in order to provide your own logic.
*
* @since 2.7.0 N°2293 can access object changes using {@link m_aChanges}
* 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

@@ -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)
{

View File

@@ -238,6 +238,12 @@ abstract class DBSearch
*/
abstract public function GetClassAlias();
/**
* @return string
* @internal
*/
abstract public function GetFirstJoinedClass();
/**
* Change the class
*

View File

@@ -161,6 +161,11 @@ class DBUnionSearch extends DBSearch
return $this->aSearches;
}
public function GetFirstJoinedClass()
{
return $this->GetClass();
}
/**
* Limited to the selected classes
*/

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'),

View File

@@ -546,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)
{
@@ -1678,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

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) || !class_exists($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,52 +470,6 @@ class FileLog
$this->oFileNameBuilder = LogFileNameBuilderFactory::GetInstance($sFileName);
}
/**
* Since 2.7.0 with the 'log_filename_builder_impl' param the logs will output to different files name
* As now by default iTop will use {@link WeeklyRotatingLogFileNameBuilder} (rotation each week), to avoid confusion, we're renaming
* the legacy error.log / setup.log.
*
* @since 2.7.0 N°2518
* @uses utils::GetConfig() the config must be persisted !
*/
public static function RenameLegacyLogFiles()
{
$oConfig = utils::GetConfig();
IssueLog::Enable(APPROOT.'log/error.log'); // refresh log file used
$sLogFileNameParam = $oConfig->Get('log_filename_builder_impl');
$aConfigValuesNoRotation = array('', 'DefaultLogFileNameBuilder');
$bIsLogRotationActivated = (!in_array($sLogFileNameParam, $aConfigValuesNoRotation, true));
if (!$bIsLogRotationActivated)
{
return;
}
IssueLog::Warning("Log name builder set to '$sLogFileNameParam', renaming legacy log files");
$aLogFilesToRename = array(
'log/setup.log' => 'log/setup.LEGACY.log',
'log/error.log' => 'log/error.LEGACY.log',
);
foreach ($aLogFilesToRename as $sLogCurrentName => $sLogNewName)
{
$sSource = APPROOT.$sLogCurrentName;
if (!file_exists($sSource))
{
IssueLog::Debug("Log file '$sLogCurrentName' (legacy) does not exists, renaming skipped");
continue;
}
$sDestination = APPROOT.$sLogNewName;
$bResult = rename($sSource, $sDestination);
if (!$bResult)
{
IssueLog::Error("Log file '$sLogCurrentName' (legacy) cannot be renamed to '$sLogNewName'");
continue;
}
IssueLog::Info("Log file '$sLogCurrentName' (legacy) renamed to '$sLogNewName'");
}
}
public function Error($sText, $sChannel = '', $aContext = array())
{
$this->Write($sText, __FUNCTION__, $sChannel, $aContext);
@@ -427,3 +698,72 @@ class ToolsLog extends LogAPI
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

@@ -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
@@ -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())
@@ -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
@@ -6757,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

View File

@@ -811,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
{
@@ -1146,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
{

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

@@ -29,6 +29,7 @@ class ormSet
protected $sClass; // class of the field
protected $sAttCode; // attcode of the field
protected $aOriginalObjects = null;
protected $m_bDisplayPartial = false;
/**
* Object from the original set, minus the removed objects
@@ -111,7 +112,7 @@ class ormSet
/**
*
* @param array $aItems
* @param string[] $aItems
*
* @throws \CoreException
* @throws \CoreUnexpectedValue when a code is invalid
@@ -126,7 +127,7 @@ class ormSet
$aValues = array();
$iCount = 0;
$bError = false;
foreach($aItems as $oItem)
foreach($aItems as $sItem)
{
$iCount++;
if (($this->iLimit != 0) && ($iCount > $this->iLimit))
@@ -134,7 +135,7 @@ class ormSet
$bError = true;
continue;
}
$aValues[] = $oItem;
$aValues[] = $sItem;
}
$this->aPreserved = &$aValues;
@@ -387,5 +388,19 @@ class ormSet
return implode(', ', $this->GetValue()) === implode(', ', $other->GetValue());
}
/**
* @return bool
*/
public function DisplayPartial()
{
return $this->m_bDisplayPartial;
}
/**
* @param bool $m_bDisplayPartial
*/
public function SetDisplayPartial($m_bDisplayPartial)
{
$this->m_bDisplayPartial = $m_bDisplayPartial;
}
}

View File

@@ -251,17 +251,27 @@ class ormStopWatch
return $sRes;
}
/**
* @param \DBObject $oObject
* @param \AttributeStopWatch $oAttDef
*
* @return float goal value (in second)
* @uses \iMetricComputer::ComputeMetric()
* @throws \CoreException
*/
protected function ComputeGoal($oObject, $oAttDef)
{
$sMetricComputer = $oAttDef->Get('goal_computing');
/** @var \iMetricComputer $oComputer */
$oComputer = new $sMetricComputer();
$aCallSpec = array($oComputer, 'ComputeMetric');
if (!is_callable($aCallSpec))
{
throw new CoreException("Unknown class/verb '$sMetricComputer/ComputeMetric'");
}
$iRet = call_user_func($aCallSpec, $oObject);
return $iRet;
return $oComputer->ComputeMetric($oObject);
}
/**

View File

@@ -26,9 +26,6 @@
*/
final class ormTagSet extends ormSet
{
private $m_bDisplayPartial = false;
/**
* ormTagSet constructor.
*
@@ -557,22 +554,4 @@ final class ormTagSet extends ormSet
{
return TagSetFieldData::GetTagDataClassName($this->sClass, $this->sAttCode);
}
/**
* @return bool
*/
public function DisplayPartial()
{
return $this->m_bDisplayPartial;
}
/**
* @param bool $m_bDisplayPartial
*/
public function SetDisplayPartial($m_bDisplayPartial)
{
$this->m_bDisplayPartial = $m_bDisplayPartial;
}
}
}

View File

@@ -457,7 +457,21 @@ class SQLObjectQuery extends SQLQuery
{
$sLimit = '';
}
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
if (count($this->__aSelectedIdFields) > 0)
{
$aCountFields = array();
foreach ($this->__aSelectedIdFields as $sFieldExpr)
{
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
}
$sCountFields = implode(', ', $aCountFields);
$sCountClause = "DISTINCT $sCountFields";
}
else
{
$sCountClause = '*';
}
$sSQL = "SELECT $sSelect,$sLineSep COUNT($sCountClause) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
return $sSQL;
}

View File

@@ -25,7 +25,7 @@
* \MFCompiler::CompileClass).<br> Only this abstract class will exists in the DB : the implementations won't had any
* new field.
*
* @since 2.6 N°931 tag fields
* @since 2.6.0 N°931 tag fields
*/
abstract class TagSetFieldData extends cmdbAbstractObject
{

View File

@@ -57,7 +57,7 @@ abstract class Trigger extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list", array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "trigger_id", "ext_key_to_remote" => "action_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array())));
$aTags = ContextTag::GetTags();
MetaModel::Init_AddAttribute( new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnum($aTags), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
MetaModel::Init_AddAttribute( new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
// Display lists
MetaModel::Init_SetZListItems('details', array('finalclass', 'description', 'context', 'action_list')); // Attributes to be displayed for the complete details
@@ -110,10 +110,11 @@ abstract class Trigger extends cmdbAbstractObject
if (!$this->IsContextValid())
{
// Trigger does not match the current context
IssueLog::Info("Context NOT valid for: ".$this->Get('friendlyname'));
$sClass = get_class($this);
$sName = $this->Get('friendlyname');
IssueLog::Debug("Context NOT valid for : {$sClass} '$sName'");
return;
}
IssueLog::Info("Context VALID for: ".$this->Get('friendlyname'));
// Find the related actions
$oLinkedActions = $this->Get('action_list');

View File

@@ -97,7 +97,7 @@ class ValueSetObjects extends ValueSetDefinition
protected $m_sFilterExpr; // in OQL
protected $m_sValueAttCode;
protected $m_aOrderBy;
protected $m_aExtraConditions;
protected $m_oExtraCondition;
private $m_bAllowAllData;
private $m_aModifierProperties;
private $m_bSort;
@@ -116,7 +116,7 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_aExtraConditions = array();
$this->m_oExtraCondition = null;
$this->m_bSort = true;
$this->m_iLimit = 0;
}
@@ -124,11 +124,22 @@ class ValueSetObjects extends ValueSetDefinition
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
$this->m_bIsLoaded = false;
}
/**
* @param \DBSearch $oFilter
* @deprecated use SetCondition
*/
public function AddCondition(DBSearch $oFilter)
{
$this->m_aExtraConditions[] = $oFilter;
$this->SetCondition($oFilter);
}
public function SetCondition(DBSearch $oFilter)
{
$this->m_oExtraCondition = $oFilter;
$this->m_bIsLoaded = false;
}
public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
@@ -141,9 +152,9 @@ class ValueSetObjects extends ValueSetDefinition
{
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
}
foreach($this->m_aExtraConditions as $oExtraFilter)
if (!is_null($this->m_oExtraCondition))
{
$oFilter = $oFilter->Intersect($oExtraFilter);
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
@@ -216,9 +227,9 @@ class ValueSetObjects extends ValueSetDefinition
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
}
if (!$oFilter) return false;
foreach($this->m_aExtraConditions as $oExtraFilter)
if (!is_null($this->m_oExtraCondition))
{
$oFilter = $oFilter->Intersect($oExtraFilter);
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
@@ -350,10 +361,10 @@ class ValueSetEnum extends ValueSetDefinition
$this->m_values = $Values;
}
// Helper to export the datat model
// Helper to export the data model
public function GetValueList()
{
$this->LoadValues($aArgs = array());
$this->LoadValues(null);
return $this->m_aValues;
}
@@ -382,6 +393,29 @@ class ValueSetEnum extends ValueSetDefinition
}
}
class ValueSetEnumPadded extends ValueSetEnum
{
public function __construct($Values)
{
parent::__construct($Values);
if (is_string($Values))
{
$this->LoadValues(null);
}
else
{
$this->m_aValues = $Values;
}
$aPaddedValues = array();
foreach ($this->m_aValues as $sKey => $sVal)
{
$sKey = str_pad($sKey, 3, '_', STR_PAD_LEFT);
$aPaddedValues[$sKey] = $sVal;
}
$this->m_values = $aPaddedValues;
}
}
/**
* Fixed set values, defined as a range: 0..59 (with an optional increment)
*

View File

@@ -0,0 +1,34 @@
/*!
*
* * 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
*
*/
$backoffice-environment-banner-background-color: #C53030 !default;
$backoffice-environment-banner-text-color: #F7FAFC !default;
$backoffice-environment-banner-text-content: "THIS IS NOT PRODUCTION INSTANCE" !default;
div#top-bar::before {
display: block;
width: 100%;
padding: 0.2rem;
text-align: center;
font-size: 1rem;
background: $backoffice-environment-banner-background-color;
color: $backoffice-environment-banner-text-color;
content: $backoffice-environment-banner-text-content;
}

View File

@@ -17,8 +17,8 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.0-beta2";
$approot-relative: "../../../../../"; // relative to env-***/branding/themes/***/main.css
$version: "v2.7.0";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors
$gray-base: #000 !default;
@@ -66,6 +66,7 @@ $frame-background-color: $gray-extra-light !default;
$text-color: #000 !default;
$box-radius: 0px !default;
$box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15) !default;
$body-background-color : $white !default;
$hyperlink-color: $complement-color !default;
$hyperlink-text-decoration: none !default;
@@ -82,6 +83,7 @@ $search-criteria-box-hover-color: $white !default;
$search-criteria-box-border-color: #CCCCCC !default;
$search-criteria-box-border: 1px solid $search-criteria-box-border-color !default;
$search-criteria-box-radius: 1px !default;
$search-criteria-more-less-details-color: #3F7294 !default;
//
$search-add-criteria-box-color: $search-criteria-box-color !default;
$search-add-criteria-box-bg-color: $white !default;
@@ -91,18 +93,25 @@ $search-button-box-color: $brand-primary !default;
$search-button-box-bg-color: $white !default;
$search-button-box-bg-hover-color: $gray-extra-light !default;
/////////////
// Buttons //
/////////////
// Toggle button
$toggle-button-bg-color: #CCCCCC !default;
$toggle-button-bg-checked-color: $brand-primary !default;
$toggle-button-slider-bg-color: #FFFFFF !default;
// Console elements
$summary-details-background: $grey-color !default;
$main-header-background: $frame-background-color !default;
$table-even-background: $frame-background-color !default;
$table-hover-background: #fdf5d0 !default;
$popup-menu-highlight-color: $highlight-color !default;
$popup-menu-text-color: #000 !default;
$popup-menu-background-color: #fff !default;
$popup-menu-text-higlight-color: #fff !default;
$breadcrumb-color: #555 !default;
$breadcrumb-text-color: #fff !default;
$breadcrumb-color: $grey-color !default;
$breadcrumb-highlight-color: $highlight-color !default;
$breadcrumb-text-highlight-color: #fff !default;
// jQuery UI widgets vars
$primary-text-color: #333333 !default;

View File

@@ -22,6 +22,7 @@
body {
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 10pt;
background-color: $body-background-color;
color: $text-color;
margin: 0; /* Remove body margin/padding */
padding: 0;
@@ -134,13 +135,13 @@ table.listResults > tbody > tr:hover > * {
table.listResults > tbody > tr.selected:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: lighten($combodo-orange, 20%);
background-color: $brand-primary-lightest;
color: $text-color;
}
table.listResults > tbody > tr:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: #fdf5d0;
background-color: $table-hover-background;
color: $text-color;
}
@@ -390,6 +391,11 @@ table.cke_dialog_contents a.cke_dialog_ui_button_ok {
.cke_notifications_area {
display: none;
}
.hljs {
padding: 0.9em !important;
box-shadow: 0 0px 3px 2px inset rgba(0, 0, 0, 0.4);
border-radius: 3px;
}
td a.no-arrow, td a.no-arrow:visited, .SearchDrawer a.no-arrow, .SearchDrawer a.no-arrow:visited {
text-decoration:none;
color: $text-color;
@@ -568,7 +574,7 @@ div.ui-accordion-content {
td a.CollapsibleLabel, a.CollapsibleLabel{
margin: 0;
padding: 0px 0pt 0px 16px;
padding: 0;
font-size:8pt;
text-decoration:none;
color:$grey-color;
@@ -576,9 +582,9 @@ td a.CollapsibleLabel, a.CollapsibleLabel{
td a.CollapsibleLabel::before, a.CollapsibleLabel::before{
font-family: "Font Awesome 5 Free";
color: $highlight-color;
content: "\f0dd";
content: "\f0da"; // caret-right
font-weight: 900;
margin-right: 6px;
margin: 0 5px;
font-size: 10px;
}
@@ -593,7 +599,7 @@ td a.CollapsibleLabel.open, a.CollapsibleLabel.open{
td a.CollapsibleLabel.open::before, a.CollapsibleLabel.open::before {
font-family: "Font Awesome 5 Free";
color: $highlight-color;
content: '\f0d7';
content: '\f0d7'; // caret-down
margin-right: 6px;
font-size: 10px;
}
@@ -1326,7 +1332,7 @@ input.dp-applied {
bottom: 4px;
right: 0px;
cursor: pointer;
color: #3F7294;
color: $search-criteria-more-less-details-color;
font-size: 10px;
font-weight: bold;
border: none;
@@ -1621,8 +1627,8 @@ input.dp-applied {
}
button{
width: 23px; /* Must be equals to .sf_filter > *:height */
background-color: $white;
color: $combodo-orange;
background-color: $search-button-box-bg-color;
color: $search-button-box-color;
font-size: 10px;
&:first-of-type{
@@ -1692,12 +1698,12 @@ table.listResults tr.even td.truncated, .wizContainer table.listResults tr.even
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.even td.hover.truncated, .wizContainer table.listResults tr.even td.hover.truncated {
background: #fdf5d0 url($approot-relative + "images/truncated.png?v=" + $version) bottom repeat-x;
background: $table-hover-background url($approot-relative + "images/truncated.png?v=" + $version) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.odd td.hover.truncated, table.listResults tr td.hover.truncated, .wizContainer table.listResults tr.odd td.hover.truncated, .wizContainer table.listResults tr td.hover.truncated {
background: #fdf5d0 url($approot-relative + "images/truncated.png?v=" + $version) bottom repeat-x;
background: $table-hover-background url($approot-relative + "images/truncated.png?v=" + $version) bottom repeat-x;
}
table.listResults.truncated {
@@ -3197,7 +3203,7 @@ span.search-button, span.refresh-button {
a{
text-decoration: none;
color: #555;
color: $breadcrumb-color;
font-size: 9pt;
padding: 0;
background: none;
@@ -3220,7 +3226,7 @@ span.search-button, span.refresh-button {
&:hover{
text-decoration: none;
color: $highlight-color;
color: $breadcrumb-highlight-color;
}
&::after{
@@ -3240,7 +3246,7 @@ span.search-button, span.refresh-button {
&.breadcrumb-current{
text-decoration: none;
color: #555;
color: $breadcrumb-color;
font-size: 9pt;
padding: 0;
background: none;
@@ -3619,7 +3625,7 @@ table.listResults .originColor{
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
background-color: $toggle-button-bg-color;
transition: .4s;
}
@@ -3630,16 +3636,16 @@ table.listResults .originColor{
width: 15px;
left: 3px;
bottom: 3px;
background-color: white;
background-color: $toggle-button-slider-bg-color;
transition: .4s;
}
input:checked + .slider {
background-color: $combodo-orange;
background-color: $toggle-button-bg-checked-color;
}
input:focus + .slider {
box-shadow: 0 0 1px $combodo-orange;
box-shadow: 0 0 1px $toggle-button-bg-checked-color;
}
input:checked + .slider:before {
@@ -3658,167 +3664,197 @@ input:checked + .slider:before {
///////////////////////////
// Newsroom menu
#newsroom_menu li span {
display: block;
padding: 5px 12px;
text-decoration: none;
white-space: nowrap;
}
#newsroom_menu li li p {
text-align: left;
margin: 2px;
margin-left: 5px;
margin-right: 5px;
cursor: pointer;
background-color: transparent;
}
#newsroom_menu > ul > li > ul {
margin-top: 8px;
}
#newsroom_menu > ul > li > ul > li {
display: block;
border-bottom: 1px $gray-lighter solid;
padding-top: 10px;
padding-bottom: 10px;
text-align: left;
}
#newsroom_menu > ul > li > ul > li:hover, #newsroom_menu > ul > li > ul > li:hover h1, #newsroom_menu > ul > li > ul > li:hover h2, #newsroom_menu > ul > li > ul > li:hover h3 {
background: $popup-menu-highlight-color;
color: $popup-menu-text-higlight-color;
}
#newsroom_menu li li:last-of-type {
border-bottom: 0;
}
#newsroom_menu li {
list-style: none;
cursor: pointer;
color: $popup-menu-text-color;
}
#newsroom_menu li ul {
width: 300px;
}
#top-left-newsroom-cell {
padding-right: 11px !important;
}
#newsroom_menu_icon {
position:relative;
top:8px;
#newsroom_menu {
/* Reset for some tags */
li {
list-style: none;
cursor: pointer;
color: $popup-menu-text-color;
}
h1 {
font-weight: 700;
font-size: 16px;
margin-bottom: 0.5em;
}
h2 {
font-weight: 700;
font-size: 14px;
margin-bottom: 0.5em;
}
h3, h4, h5 {
font-weight: 700;
font-size: 12px;
margin-bottom: 0.5em;
}
#newsroom_menu_icon {
position:relative;
top:8px;
}
> ul > li {
> ul {
margin-top: 8px;
width: 300px;
> li {
display: block;
border-bottom: 1px $gray-lighter solid;
padding-top: 10px;
padding-bottom: 10px;
text-align: left;
&#newsroom_menu_dismiss_all,
&#newsroom_menu_show_all {
text-align: center !important;
}
#newsroom_show_all_submenu {
text-align: center;
padding-top: 0 !important;
padding-bottom: 0 !important;
> ul {
padding-top: 10px;
padding-bottom: 10px;
> li > ul {
margin-top: 8px;
width: 200px;
color:$text-color;
> li {
display: block;
border-bottom: 1px $gray-lighter solid;
padding-left: 5px;
padding-top: 4px;
padding-bottom: 6px;
text-align: left;
&:hover {
background: $popup-menu-highlight-color;
color: $popup-menu-text-higlight-color;
}
}
}
}
}
&#newsroom_menu_show_all,
#newsroom_show_all_submenu{
.newsroom_extra_messages_counter {
display:inline-block !important;
padding:2px !important;
border-radius:8px;
text-align:center;
font-size: 8pt;
min-width: 12px;
background-color:$gray-light;
color: $white;
float: right;
margin-right: 5px;
}
}
&.newsroom_menu_item{
/* Icon */
> div > img {
float:left;
width:32px;
max-height:32px;
margin: 0 5px;
}
/* Content */
p {
text-align: left;
margin: 2px 5px 10px 5px;
cursor: pointer;
background-color: transparent;
img {
max-width: 100%;
}
&:last-of-type{
margin-bottom: 0;
}
}
/* Provider and date */
span {
display: block;
padding: 5px 12px;
text-decoration: none;
white-space: nowrap;
&.newsroom_menu_item_date {
padding: 0 !important;
margin-bottom: -10px;
font-size: 8pt;
text-align: right;
margin-top: -2px;
margin-right: 5px;
color: $white;
}
}
}
&:last-of-type{
border-bottom: 0;
}
&:hover,
&:hover h1,
&:hover h2,
&:hover h3{
background: $popup-menu-highlight-color;
color: $popup-menu-text-higlight-color;
}
}
}
}
}
#newsroom_menu_counter_container {
position:relative;
top:-23px;
left:3px;
}
#newsroom_menu_counter {
display:inline-block;
padding:2px;
border-radius:8px;
text-align:center;
width: 12px;
box-shadow: 1px 1px 2px 0px $gray;
font-size: 8pt;
background-color:$complement-color;
color: $white;
cursor: pointer;
}
.newsroom_extra_messages_counter {
display:inline-block !important;
padding:2px !important;
border-radius:8px;
text-align:center;
font-size: 8pt;
min-width: 12px;
background-color:$gray-light;
color: $white;
float: right;
margin-right: 5px;
}
.newsroom_menu_item > div > img {
float:left;
width:32px;
max-height:32px;
margin-top: 0;
margin-bottom: 0;
margin-left:5px;
margin-right: 5px;
}
#newsroom_menu_dismiss_all, #newsroom_menu_show_all {
text-align: center !important;
}
#newsroom_show_all_submenu {
text-align: center;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
#newsroom_show_all_submenu > ul {
padding-top: 10px;
padding-bottom: 10px;
}
#newsroom_show_all_submenu > ul > li > ul {
margin-top: 8px;
}
#newsroom_show_all_submenu > ul > li > ul > li {
display: block;
border-bottom: 1px $gray-lighter solid;
padding-left: 5px;
padding-top: 4px;
padding-bottom: 6px;
text-align: left;
}
#newsroom_show_all_submenu > ul > li > ul > li:hover {
background: $popup-menu-highlight-color;
color: $popup-menu-text-higlight-color;
}
#newsroom_show_all_submenu li ul {
width: 200px;
color:$text-color;
}
.no-padding {
padding: 0 !important;
}
.newsroom_menu_item_date {
padding: 0 !important;
margin-bottom: -10px;
font-size: 8pt;
text-align: right;
margin-top: -2px;
margin-right: 5px;
color: $white;
#newsroom_menu_counter {
display:inline-block;
padding:2px;
border-radius:8px;
text-align:center;
width: 12px;
box-shadow: 1px 1px 2px 0px $gray;
font-size: 8pt;
background-color:$complement-color;
color: $white;
cursor: pointer;
}
}
#newsroom_display_size {
height: 1em;
width: 3em;
}
#newsroom_no_new_message p {
text-align: center !important;
cursor: default !important;
font-style: italic;
}
#newsroom_no_new_message {
color: $gray-dark !important;
}
#newsroom_no_new_message:hover {
cursor: default !important;
color: $gray-dark !important;
background-color: $white !important;
}
#newsroom_menu h1 {
font-weight: 700;
font-size: 16px;
margin-bottom: 0.5em;
}
#newsroom_menu h2 {
font-weight: 700;
font-size: 14px;
margin-bottom: 0.5em;
}
#newsroom_menu h3, #newsroom_menu h4, #newsroom_menu h5 {
font-weight: 700;
font-size: 12px;
margin-bottom: 0.5em;
}
p {
text-align: center !important;
cursor: default !important;
font-style: italic;
}
&:hover {
cursor: default !important;
color: $gray-dark !important;
background-color: $white !important;
}
}
/* Helpers */
.no-padding {
padding: 0 !important;
}
.clearboth {
clear: both;
}

View File

@@ -1,4 +1,4 @@
/*
/*!
* Copyright (C) 2013-2020 Combodo SARL
*
* This file is part of iTop.
@@ -60,11 +60,12 @@ h1 {
}
h2 {
color: #1A202C;
font-size: 12pt;
font-size: 14pt;
font-weight: normal;
}
h3 {
color: #1C94C4;
font-size: 11pt;
font-size: 12pt;
font-weight: bold;
}
a {

View File

@@ -93,11 +93,12 @@ h1 {
}
h2 {
color: $default-font-color;
font-size: 12pt;
font-size: 14pt;
font-weight: normal;
}
h3 {
color: #1C94C4;
font-size: 11pt;
font-size: 12pt;
font-weight: bold;
}
a{

View File

@@ -1,7 +1,7 @@
# Apache 2.4
<ifModule mod_authz_core.c>
Require all denied
<FilesMatch ".+\.(css|scss|js|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html)$">
<FilesMatch ".+\.(css|scss|js|map|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html)$">
Require all granted
</FilesMatch>
</ifModule>
@@ -10,7 +10,7 @@ Require all denied
<ifModule !mod_authz_core.c>
deny from all
Satisfy All
<FilesMatch ".+\.(css|scss|js|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html)$">
<FilesMatch ".+\.(css|scss|js|map|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html)$">
Order Allow,Deny
Allow from all
</FilesMatch>

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DA DA', 'Danish', 'Dansk', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design version="1.7">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
<constants>
</constants>
<classes>

View File

@@ -2,12 +2,11 @@
/**
* Localized data
*
* @copyright Copyright (C) 2013 XXXXX
* @copyright Copyright (C) 2019 ITOMIG GmbH
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('DE DE', 'German', 'Deutsch', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',
'CAS:Login:SignInTooltip' => 'Click here to authenticate yourself with the CAS server~~',
'CAS:Error:UserNotAllowed' => 'Benutzer ist nicht zugelassen',
'CAS:Login:SignIn' => 'Anmeldung mit CAS',
'CAS:Login:SignInTooltip' => 'Hier klicken um sich am CAS-Server zu authentifizieren',
));

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('FR FR', 'French', 'Français', array(
'CAS:Error:UserNotAllowed' => 'Utilisateur non autorisé',
'CAS:Login:SignIn' => 'S\'identifier avec CAS',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('IT IT', 'Italian', 'Italiano', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('JA JP', 'Japanese', '日本語', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -6,7 +6,6 @@
* @license http://opensource.org/licenses/AGPL-3.0
* @author Jeffrey Bostoen - <jbostoen.itop@outlook.com> (2018 - 2020)
*/
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'CAS:Error:UserNotAllowed' => 'Gebruiker heeft onvoldoende rechten.',
'CAS:Login:SignIn' => 'Inloggen met CAS',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -7,7 +7,6 @@
* @license http://opensource.org/licenses/AGPL-3.0
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', array(
'CAS:Error:UserNotAllowed' => 'Вход не разрешён',
'CAS:Login:SignIn' => 'Вход через CAS',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',

View File

@@ -5,7 +5,6 @@
* @copyright Copyright (C) 2013 XXXXX
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'CAS:Error:UserNotAllowed' => '用户被禁止登录',
'CAS:Login:SignIn' => '使用CAS 登录',

View File

@@ -4,7 +4,7 @@
<module_parameters>
<parameters id="authent-local" _delta="define">
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$</password_validation.pattern>
<password_validation.classes></password_validation.classes>
<password_validation.message type="hash"></password_validation.message>
</parameters>
</module_parameters>
</itop_design>

View File

@@ -28,18 +28,18 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:UserLocal/Attribute:password' => 'Passwort',
'Class:UserLocal/Attribute:password+' => 'Benutzerpasswort',
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
'Class:UserLocal/Attribute:expiration/Value:can_expire+' => '~~',
'Class:UserLocal/Attribute:expiration/Value:never_expire' => 'Never expire~~',
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '~~',
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'Expired~~',
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '~~',
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
'Class:UserLocal/Attribute:expiration' => 'Passwortablauf',
'Class:UserLocal/Attribute:expiration+' => 'Passwortablaufstatus (Statusabhängige Effekte müssen per Extension implementiert werden)',
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'kann ablaufen',
'Class:UserLocal/Attribute:expiration/Value:can_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:never_expire' => 'Läuft nie ab',
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'abgelaufen',
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
'Class:UserLocal/Attribute:password_renewed_date' => 'Passworterneuerung',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 8 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',
'UserLocal:password:expiration' => 'The fields below require an extension~~'
'UserLocal:password:expiration' => 'Die folgenden Felder benötigen eine iTop Erweiterung'
));

View File

@@ -38,5 +38,5 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
'UserLocal:password:expiration' => 'Le champ requiert une extension'
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension'
));

View File

@@ -100,7 +100,7 @@ class UserLocal extends UserInternal
array(
'col:col1' =>
array(
'contactid', 'org_id', 'email', 'login', 'password', 'language', 'status', 'profile_list', 'allowed_org_list',
'fieldset:UserLocal:info' => array('contactid', 'org_id', 'email', 'login', 'password', 'language', 'status', 'profile_list', 'allowed_org_list',)
),
'col:col2' =>
array(
@@ -169,14 +169,42 @@ class UserLocal extends UserInternal
if ('password' == $sAttCode)
{
$sNow = date(\AttributeDate::GetInternalFormat());
$this->Set('password_renewed_date', $sNow);
$this->ValidatePassword($value);
}
return $result;
}
protected function OnUpdate()
{
parent::OnUpdate();
$this->OnWrite();
}
protected function OnInsert()
{
parent::OnInsert();
$this->OnWrite();
}
protected function OnWrite()
{
if (empty($this->m_oPasswordValidity))
{
return;
}
if (array_key_exists('password_renewed_date', $this->ListChanges()))
{
return;
}
$sNow = date(\AttributeDate::GetInternalFormat());
$this->Set('password_renewed_date', $sNow);
}
public function IsPasswordValid()
{
if (ContextTag::Check(ContextTag::TAG_SETUP))
@@ -188,6 +216,7 @@ class UserLocal extends UserInternal
return (empty($this->m_oPasswordValidity)) || ($this->m_oPasswordValidity->isPasswordValid());
}
public function getPasswordValidityMessage()
{
if (ContextTag::Check(ContextTag::TAG_SETUP))
@@ -204,7 +233,6 @@ class UserLocal extends UserInternal
return $this->m_oPasswordValidity->getPasswordValidityMessage();
}
/**
* set the $m_oPasswordValidity based on UserLocalPasswordValidator instances vote.
*
@@ -265,7 +293,7 @@ class UserLocal extends UserInternal
{
if (strpos('contactid,login,language,password,status,profile_list,allowed_org_list', $sAttCode) !== false)
{
// contactid and allowed_org_list are disabled to make sure the portal remains accessible
// contactid and allowed_org_list are disabled to make sure the portal remains accessible
$aReasons[] = 'Sorry, this attribute is read-only in the demonstration mode!';
$iFlags |= OPT_ATT_READONLY;
}
@@ -325,7 +353,24 @@ class UserPasswordPolicyRegex implements UserLocalPasswordValidator
return new UserLocalPasswordValidity(true);
}
$sMessage = Dict::S('Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed');
$sUserLanguage = Dict::GetUserLanguage();
$customMessages = $config->GetModuleSetting('authent-local', 'password_validation.message', null);
if (is_string($customMessages) )
{
$sMessage = $customMessages;
}
elseif (isset($customMessages) && array_key_exists($sUserLanguage, $customMessages))
{
$sMessage = $customMessages[$sUserLanguage];
}
elseif (isset($customMessages) && array_key_exists('EN US', $customMessages))
{
$sMessage = $customMessages['EN US'];
}
else
{
$sMessage = Dict::S('Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed');
}
return new UserLocalPasswordValidity(
false,

View File

@@ -21,7 +21,6 @@
* 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: UserLocal
//
@@ -44,6 +43,5 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 8 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.',
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.'
));

View File

@@ -3,7 +3,7 @@
<menus>
<menu id="DBToolsMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>30</rank>
<parent>System</parent>
<parent>SystemTools</parent>
<url>$pages/exec.php?exec_module=combodo-db-tools&amp;exec_page=dbtools.php&amp;c[menu]=DBToolsMenu</url>
<enable_admin_only>1</enable_admin_only>
</menu>

View File

@@ -104,10 +104,6 @@ class DatabaseAnalyzer
*/
public function CheckIntegrity($aClassSelection)
{
// Getting and setting time limit are not symetric:
// www.php.net/manual/fr/function.set-time-limit.php#72305
$iPreviousTimeLimit = ini_get('max_execution_time');
$aErrorsAndFixes = array();
if (empty($aClassSelection))
@@ -125,13 +121,10 @@ class DatabaseAnalyzer
$aClassSelection = array_unique($aClassSelection);
}
foreach($aClassSelection as $sClass)
foreach ($aClassSelection as $sClass)
{
// Check uniqueness rules
if (method_exists('MetaModel', 'GetUniquenessRules'))
{
$this->CheckUniquenessRules($sClass, $aErrorsAndFixes);
}
$this->CheckUniquenessRules($sClass, $aErrorsAndFixes);
if (!MetaModel::HasTable($sClass))
{
@@ -144,13 +137,16 @@ class DatabaseAnalyzer
if (!MetaModel::IsStandaloneClass($sClass))
{
$sRootTable = MetaModel::DBGetTable($sRootClass);
$sRootKey = MetaModel::DBGetKey($sRootClass);
if (!MetaModel::IsRootClass($sClass))
{
$sRootTable = MetaModel::DBGetTable($sRootClass);
$sRootKey = MetaModel::DBGetKey($sRootClass);
$this->CheckRecordsInRootTable($sTable, $sKeyField, $sRootTable, $sRootKey, $sClass, $aErrorsAndFixes);
$this->CheckRecordsInChildTable($sRootClass, $sClass, $sRootTable, $sRootKey, $sTable, $sKeyField, $aErrorsAndFixes);
if (!MetaModel::IsLeafClass($sClass))
{
$this->CheckIntermediateFinalClass($sRootClass, $sClass, $sRootTable, $sRootKey, $sTable, $sKeyField, $aErrorsAndFixes);
}
}
}
@@ -174,10 +170,6 @@ class DatabaseAnalyzer
}
$this->CheckUsers($aErrorsAndFixes);
if (!is_null($this->iTimeLimitPerOperation))
{
set_time_limit($iPreviousTimeLimit);
}
return $aErrorsAndFixes;
}
@@ -317,6 +309,33 @@ class DatabaseAnalyzer
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, Dict::Format('DBAnalyzer-Integrity-OrphanRecord', $sRootTable, $sTable), $sRootClass, $aErrorsAndFixes);
}
/**
* Check that the "finalclass" field is correct for all the classes of the hierarchy
*
* @param $sRootClass
* @param $sClass
* @param $sRootTable
* @param $sRootKey
* @param $sTable
* @param $sKeyField
* @param $aErrorsAndFixes
*
* @throws \CoreException
*/
private function CheckIntermediateFinalClass($sRootClass, $sClass, $sRootTable, $sRootKey, $sTable, $sKeyField, &$aErrorsAndFixes)
{
$sField = MetaModel::DBGetClassField($sClass);
$sRootField = MetaModel::DBGetClassField($sRootClass);
$sSelWrongRecs = <<<SQL
SELECT `$sTable`.`$sKeyField` AS id
FROM `$sTable`
JOIN `$sRootTable` ON `$sRootTable`.`$sRootKey` = `$sTable`.`$sKeyField`
WHERE `$sTable`.`$sField` != `$sRootTable`.`$sRootField`
SQL;
// Copy the finalclass of the root table
$sFixItRequest = "UPDATE `$sTable`,`$sRootTable` SET `$sTable`.`$sField` = `$sRootTable`.`$sRootField` WHERE `$sTable`.`$sKeyField` = `$sRootTable`.`$sRootKey`";
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, Dict::Format('DBAnalyzer-Integrity-FinalClass', $sField, $sTable, $sRootTable), $sClass, $aErrorsAndFixes);
}
/**
* Check that any external field is pointing to an existing object
*
@@ -455,6 +474,8 @@ class DatabaseAnalyzer
}
/**
* Check user accounts without profile
*
* @param $aErrorsAndFixes
*
* @throws \CoreException
@@ -462,7 +483,6 @@ class DatabaseAnalyzer
*/
private function CheckUsers(&$aErrorsAndFixes)
{
// Check user accounts without profile
$sUserTable = MetaModel::DBGetTable('User');
$sLinkTable = MetaModel::DBGetTable('URP_UserProfile');
$sSelect = "SELECT DISTINCT u.id AS id, u.`login` AS value";

View File

@@ -27,7 +27,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'DBTools:Error' => 'Fehler',
'DBTools:Count' => 'Anzahl',
'DBTools:SQLquery' => 'SQL Query',
'DBTools:FixitSQLquery' => 'SQL query To Fix it (indication)~~',
'DBTools:FixitSQLquery' => 'SQL Query zur Fehlerbehebung (Indikation)',
'DBTools:SQLresult' => 'SQL Ergebnis',
'DBTools:NoError' => 'Die Datenbank ist OK',
'DBTools:HideIds' => 'Fehler',

View File

@@ -52,6 +52,8 @@ Dict::Add('EN US', 'English', 'English', array(
'DBAnalyzer-Integrity-InvalidValue' => 'Invalid value for %1$s (column: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-UsersWithoutProfile' => 'Some user accounts have no profile at all',
'DBAnalyzer-Fetch-Count-Error' => 'Fetch count error in `%1$s`, %2$d entries fetched / %3$d counted',
'DBAnalyzer-Integrity-FinalClass' => 'Field `%2$s`.`%1$s` must have the same value than `%3$s`.`%1$s`',
'DBAnalyzer-Integrity-RootFinalClass' => 'Field `%2$s`.`%1$s` must contains a valid class',
));
// Database Info

View File

@@ -47,6 +47,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'DBAnalyzer-Integrity-InvalidValue' => 'Valeur invalide pour %1$s (colonne: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-UsersWithoutProfile' => 'Certains comptes utilisateurs n\'ont aucun profile',
'DBAnalyzer-Fetch-Count-Error' => 'Erreur de récupération dans `%1$s`, %2$d enregistrements récupérés / %3$d comptés',
'DBAnalyzer-Integrity-FinalClass' => 'Le champ `%2$s`.`%1$s` doit avoir la même valeur que `%3$s`.`%1$s`',
'DBAnalyzer-Integrity-RootFinalClass' => 'Le champ `%2$s`.`%1$s` doit contenir une classe valide',
));
// Database Info

View File

@@ -20,7 +20,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
// Database inconsistencies
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
// Dictionary entries go here

View File

@@ -20,7 +20,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
// Database inconsistencies
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
// Dictionary entries go here

View File

@@ -0,0 +1,4 @@
#
# The following source files are not re-distributed with the "build" of the application
# since they are used solely for constructing other files during the build process
#

View File

@@ -36,9 +36,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Attachment:Max_Ko' => '(Maximale Dateigröße: %1$s KB)',
'Attachments:NoAttachment' => 'Kein Attachment. ',
'Attachments:PreviewNotAvailable' => 'Vorschau für diesen Attachment-Typ nicht verfügbar.',
'Attachments:Error:FileTooLarge' => 'File is too large to be uploaded. %1$s~~',
'Attachments:Render:Icons' => 'Display as icons~~',
'Attachments:Render:Table' => 'Display as list~~',
'Attachments:Error:FileTooLarge' => 'Die Datei ist zu groß für den Upload: %1$s',
'Attachments:Render:Icons' => 'Als Icons anzeigen',
'Attachments:Render:Table' => 'Als Liste anzeigen',
));
//
@@ -46,40 +46,40 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
//
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Attachment' => 'Attachment~~',
'Class:Attachment+' => '~~',
'Class:Attachment/Attribute:expire' => 'Expire~~',
'Class:Attachment/Attribute:expire+' => '~~',
'Class:Attachment/Attribute:temp_id' => 'Temporary id~~',
'Class:Attachment/Attribute:temp_id+' => '~~',
'Class:Attachment/Attribute:item_class' => 'Item class~~',
'Class:Attachment/Attribute:item_class+' => '~~',
'Class:Attachment/Attribute:item_id' => 'Item~~',
'Class:Attachment/Attribute:item_id+' => '~~',
'Class:Attachment/Attribute:item_org_id' => 'Item organization~~',
'Class:Attachment/Attribute:item_org_id+' => '~~',
'Class:Attachment/Attribute:contents' => 'Contents~~',
'Class:Attachment/Attribute:contents+' => '~~',
'Class:Attachment' => 'Attachment',
'Class:Attachment+' => '',
'Class:Attachment/Attribute:expire' => 'Läuft ab',
'Class:Attachment/Attribute:expire+' => '',
'Class:Attachment/Attribute:temp_id' => 'Temporäre ID',
'Class:Attachment/Attribute:temp_id+' => '',
'Class:Attachment/Attribute:item_class' => 'Itemklasse',
'Class:Attachment/Attribute:item_class+' => '',
'Class:Attachment/Attribute:item_id' => 'Item',
'Class:Attachment/Attribute:item_id+' => '',
'Class:Attachment/Attribute:item_org_id' => 'Item Organisation',
'Class:Attachment/Attribute:item_org_id+' => '',
'Class:Attachment/Attribute:contents' => 'Inhalt',
'Class:Attachment/Attribute:contents+' => '',
));
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Attachments:File:Thumbnail' => 'Icon~~',
'Attachments:File:Name' => 'File name~~',
'Attachments:File:Date' => 'Upload date~~',
'Attachments:File:Uploader' => 'Uploaded by~~',
'Attachments:File:Size' => 'Size~~',
'Attachments:File:MimeType' => 'Type~~',
'Attachments:File:Thumbnail' => 'Icon',
'Attachments:File:Name' => 'Dateiname',
'Attachments:File:Date' => 'Upload-Datum',
'Attachments:File:Uploader' => 'hochgeladen von',
'Attachments:File:Size' => 'Größe',
'Attachments:File:MimeType' => 'Typ',
));
//
// Class: Attachment
//
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Attachment/Attribute:creation_date' => 'Creation date~~',
'Class:Attachment/Attribute:creation_date+' => '~~',
'Class:Attachment/Attribute:user_id' => 'User id~~',
'Class:Attachment/Attribute:user_id+' => '~~',
'Class:Attachment/Attribute:contact_id' => 'Contact id~~',
'Class:Attachment/Attribute:contact_id+' => '~~',
'Class:Attachment/Attribute:creation_date' => 'Erstellungsdatum',
'Class:Attachment/Attribute:creation_date+' => '',
'Class:Attachment/Attribute:user_id' => 'Benutzer ID',
'Class:Attachment/Attribute:user_id+' => '',
'Class:Attachment/Attribute:contact_id' => 'Kontakt ID',
'Class:Attachment/Attribute:contact_id+' => '',
));

View File

@@ -3,7 +3,7 @@
<menus>
<menu id="BackupStatus" xsi:type="WebPageMenuNode" _delta="define">
<rank>50</rank>
<parent>System</parent>
<parent>SystemTools</parent>
<url>status.php</url>
<enable_admin_only>1</enable_admin_only>
</menu>

View File

@@ -20,7 +20,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'bkp-backup-running' => '备份正在进行,请稍后...',

View File

@@ -20,7 +20,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Menu:ChangeManagement' => '变更管理',
'Menu:Change:Overview' => '概况',

View File

@@ -20,7 +20,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Menu:ChangeManagement' => '变更管理',
'Menu:Change:Overview' => '概况',

View File

@@ -1898,6 +1898,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Server:otherinfo' => 'Další informace',
'Server:power' => 'Napájení',
'Person:info' => 'Obecné informace',
'UserLocal:info' => 'General information~~',
'Person:personal_info' => 'Personal information~~',
'Person:notifiy' => 'Upozornění',
'Class:Subnet/Tab:IPUsage' => 'Využití IP',

View File

@@ -1891,6 +1891,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Server:otherinfo' => 'Øvrig Information',
'Server:power' => 'Power supply~~',
'Person:info' => 'Almindelig Information',
'UserLocal:info' => 'General information~~',
'Person:personal_info' => 'Personal information~~',
'Person:notifiy' => 'Underretning',
'Class:Subnet/Tab:IPUsage' => 'IP Brug',

File diff suppressed because one or more lines are too long

View File

@@ -8530,36 +8530,21 @@
<stylesheet id="main">../css/light-grey.scss</stylesheet>
</stylesheets>
</theme>
<theme id="basque-red" _delta="define">
<variables>
<variable id="brand-primary">#C53030</variable>
<variable id="hover-background-color">#F6F6F6</variable>
<variable id="icons-filter">grayscale(1)</variable>
<variable id="search-form-container-bg-color">#4A5568</variable>
</variables>
<imports>
<import id="css-variables">../css/css-variables.scss</import>
</imports>
<stylesheets>
<stylesheet id="jqueryui">../css/ui-lightness/jqueryui.scss</stylesheet>
<stylesheet id="main">../css/light-grey.scss</stylesheet>
</stylesheets>
</theme>
<theme id="ocean-blue" _delta="define">
<variables>
<variable id="brand-primary">#2B6CB0</variable>
<variable id="hover-background-color">#F6F6F6</variable>
<variable id="icons-filter">hue-rotate(-139deg)</variable>
<variable id="search-form-container-bg-color">#2C5282</variable>
</variables>
<imports>
<import id="css-variables">../css/css-variables.scss</import>
</imports>
<stylesheets>
<stylesheet id="jqueryui">../css/ui-lightness/jqueryui.scss</stylesheet>
<stylesheet id="main">../css/light-grey.scss</stylesheet>
</stylesheets>
</theme>
<theme id="test-red" _delta="define">
<variables>
<variable id="backoffice-environment-banner-background-color">#C53030</variable>
<variable id="backoffice-environment-banner-text-color">#F7FAFC</variable>
<variable id="backoffice-environment-banner-text-content">"THIS IS A TEST INSTANCE"</variable>
</variables>
<imports>
<import id="css-variables">../css/css-variables.scss</import>
</imports>
<stylesheets>
<stylesheet id="jqueryui">../css/ui-lightness/jqueryui.scss</stylesheet>
<stylesheet id="main">../css/light-grey.scss</stylesheet>
<stylesheet id="environment-banner">../css/backoffice-environment-banner.scss</stylesheet>
</stylesheets>
</theme>
</themes>
</branding>
</itop_design>

View File

@@ -1893,6 +1893,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Server:otherinfo' => 'Sonstige Informationen',
'Server:power' => 'Stromversorgung',
'Person:info' => 'Allgemeine Informationen',
'UserLocal:info' => 'Allgemeine Informationen',
'Person:personal_info' => 'Persönliche Informationen',
'Person:notifiy' => 'Benachrichtigungen',
'Class:Subnet/Tab:IPUsage' => 'IP-Nutzung',

View File

@@ -1897,6 +1897,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Server:otherinfo' => 'Other information',
'Server:power' => 'Power supply',
'Person:info' => 'General information',
'UserLocal:info' => 'General information',
'Person:personal_info' => 'Personal information',
'Person:notifiy' => 'Notification',
'Class:Subnet/Tab:IPUsage' => 'IP Usage',

View File

@@ -1897,6 +1897,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
'Server:otherinfo' => 'Otra Información',
'Server:power' => 'Fuente de Poder',
'Person:info' => 'Información General',
'UserLocal:info' => 'Información General',
'Person:personal_info' => 'Información Personal',
'Person:notifiy' => 'Notificación',
'Class:Subnet/Tab:IPUsage' => 'Uso de IP',

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