Compare commits

...

618 Commits
2.3.0 ... 2.4.2

Author SHA1 Message Date
Guillaume Lajarige
7c78acbe25 Creating SVN tag for iTop 2.4.2 (Forgot to create it at the time)
SVN:2.4.2[5922]
2018-06-28 08:26:18 +00:00
Romain Quetiez
fe887f01c0 Reelasing 2.4.2
SVN:2.4[5867]
2018-06-14 09:33:29 +00:00
Eric Espié
9109138127 N°1420 - Fix index generation
SVN:2.4[5866]
2018-06-14 09:03:39 +00:00
Eric Espié
51b19c03b1 N°1420 - Fix index generation
SVN:2.4[5865]
2018-06-14 08:56:12 +00:00
Eric Espié
8cb584abdd N°1420 - Fix index generation
SVN:2.4[5842]
2018-06-08 13:01:45 +00:00
Eric Espié
3480d478b0 N°1420 - Limit count
SVN:2.4[5839]
2018-06-08 09:30:10 +00:00
Eric Espié
3a34417a7d N°1420 - Limit count
SVN:2.4[5838]
2018-06-08 09:24:25 +00:00
Eric Espié
6ca3ca108b N°1420 - Fix index generation
SVN:2.4[5837]
2018-06-08 09:23:30 +00:00
Eric Espié
037036ce6a N°1420 - don't launch the search automatically for n:n and n:1 links for huge tables
SVN:2.4[5832]
2018-06-07 12:20:22 +00:00
Eric Espié
9d2dab5eba N°1420 - Performance for auto-complete widgets
SVN:2.4[5829]
2018-06-06 07:51:07 +00:00
Eric Espié
3169b39952 N°1488 - restore failed on production-modules
SVN:2.4[5827]
2018-06-05 13:48:56 +00:00
Eric Espié
ba33b01d84 SVN:2.4[5820] 2018-05-30 10:24:07 +00:00
Eric Espié
7db69ed158 N°1420 - Autocomplete
Directory 'branch-2.4' created by PhpStorm

SVN:2.4[5819]
2018-05-30 10:20:10 +00:00
Guillaume Lajarige
a6f6d536e0 N°1472 Portal: OQL optimization in ManageBrick when several UNIONs are used.
SVN:2.4[5813]
2018-05-23 15:03:33 +00:00
Stephen Abello
880faf2021 (Retrofit from trunk) N°1444 : fixed regression introduced in [r5725] & [r5774]
SVN:2.4[5810]
2018-05-23 11:29:56 +00:00
Eric Espié
f96902c03e N°1330 - Fix broken sql requests due to the use of class instead of alias
SVN:2.4[5783]
2018-05-15 12:11:22 +00:00
Pierre Goiffon
6e810322f7 (Retrofit from trunk) N°1418 fix audit with valid_flag=true that were always failing (r5773)
SVN:2.4[5774]
2018-05-04 15:12:55 +00:00
Guillaume Lajarige
dd9984be7f (Retrofit from trunk) N°1425 Fix regression introduced in 2.4. Creation of an object in a specific state could result in a fatal error due to bad ormlinkSet initialization.
SVN:2.4[5756]
2018-04-27 14:20:44 +00:00
Stephen Abello
98f3f88ea2 N°1424 : (Retrofit from trunk r5339 & r5751) Sharing base compatibility fix
SVN:2.4[5752]
2018-04-27 12:32:28 +00:00
Pierre Goiffon
8be0c36859 (Retrofit from trunk) N°1418 Audits Perf optimization for AuditRule with valid_flag=true and lots of negative records
Use a new helper method that don't parse values anymore on SELECT IN / NOT IN queries (r5724)

SVN:2.4[5725]
2018-04-23 14:54:19 +00:00
Pierre Goiffon
8c5a65d836 (Retrofit from trunk) N°1328 Fix CSV import : check if user has rights on imported class (r5597)
SVN:2.4[5598]
2018-04-03 13:40:47 +00:00
Pierre Goiffon
db47973063 (Retrofit from trunk) N°1330 Header with statistics dashlet perf improvements
Now uses one count + group by query instead of one count query per grouping value (r5576)

SVN:2.4[5577]
2018-03-29 15:50:04 +00:00
Eric Espié
028e815bfa Retrofit from trunk
Magic trick for windows. Sometimes the folder is empty but rmdir fails [from revision 5344]

SVN:2.4[5347]
2018-02-13 11:03:44 +00:00
Denis Flaven
caf07affa6 (retrofit from trunk) Cleanup target build directory before building into it...
SVN:2.4[5346]
2018-02-13 11:02:52 +00:00
Denis Flaven
bdef9e59de (retrofit from trunk) Bug fixes:
- support an upgrade of a givne component (same directory in data/production-modules)
- deployment no longer blocked after a failed attempt (cleanup of the data/production-build-modules directory)
- load of the "structural data" of newly added extensions

Enhancements:
- All traces go to log/setup.log, and  traces have been added to clearly identify the different phases of the deployment.

SVN:2.4[5342]
2018-02-12 12:33:47 +00:00
Denis Flaven
4bf001d108 The Hub is alive ! Let's use the production URL.
SVN:2.4[5338]
2018-02-09 14:15:31 +00:00
Denis Flaven
a095208e53 N°1323: Bug fix for a crash with the error message: class 'cmdbAbstractObject' not found, in the last screen of the setup under very specific circumstances.
SVN:2.4[5335]
2018-02-09 14:03:40 +00:00
Denis Flaven
2429ec4eec N°1323: Bug fix for a crash with the error message: class 'cmdbAbstractObject' not found, in the last screen of the setup under very specific circumstances.
SVN:2.4[5334]
2018-02-09 13:46:23 +00:00
Denis Flaven
55655e3fca Added an extra safety check to detect inconsistencies between the added extensions and the choices made during the initial installation.
SVN:2.4[5319]
2018-02-09 08:53:26 +00:00
Romain Quetiez
7e98b04ed3 (retrofit from trunk) N°1287 Update the installation instructions (pointing to the wiki page), and fix the readme to point to the correct wiki pages
SVN:2.4[5296]
2018-01-25 13:58:48 +00:00
Denis Flaven
59c6c0250d (retrofit from trunk) Bug fixes:
- properly detect missing dependencies when deploying extensions from the Hub (and not only when deploying a 2nd time an extension, cf bug n°1284).
- setup hangs when upgrading to 2.4.1 with some "old" extensions in the "extensions" folder.

SVN:2.4[5291]
2018-01-23 10:39:47 +00:00
Pierre Goiffon
18a5afb8a6 (Retrofit from trunk) N°942 PHP version not yet validated was incorrectly set to 7.1.9, fix it back to 7.2.0 (r5288)
SVN:2.4[5289]
2018-01-18 11:12:41 +00:00
Denis Flaven
4b6258dfa1 (Retrofit from trunk) Bug fix: do not (try to) launch the backup if the backup detection told us that the backup is not possible!
SVN:2.4[5287]
2018-01-17 16:37:39 +00:00
Denis Flaven
44b9f23117 (Retrofit from trunk) Fix for a problem breaking the mysqldump detection (when it fails on windows). Root cause: do not return/display the output of the shell command used to test mysqldump since (on windows) it may contain non-UTF-8 characters of an unknown character set and this breaks "UTF-8 picky" functions like json_encode.
SVN:2.4[5286]
2018-01-17 16:34:36 +00:00
Pierre Goiffon
2ddefcd2fc (Retrofit from trunk) Remove file that do not belongs here (r5282)
SVN:2.4[5283]
2018-01-17 14:51:52 +00:00
Pierre Goiffon
39b15fec50 (Retrofit from trunk) Change GetSQLQueryStructure() method visibility (r5187)
SVN:2.4[5281]
2018-01-17 13:47:01 +00:00
Romain Quetiez
c180dd9863 (retrofit from trunk) Getting ready for the release of 2.4.1 in february
SVN:2.4[5280]
2018-01-17 13:12:49 +00:00
Denis Flaven
1172130d7c (Rtrofit from trunk) Setup: special mapping for 2 extensions which code has changed...
SVN:2.4[5278]
2018-01-17 12:38:42 +00:00
Denis Flaven
8172e14345 (Retrofit from trunk) The Hub Connects !!
Adding iTop Hub Connector.

SVN:2.4[5275]
2018-01-17 10:23:51 +00:00
Denis Flaven
6e4836f5e0 (Retrofit from trunk) Preparing for the Hub: better decouple the RunTimeEnvironment from the list of directories to scan/install in order to support installation from the Hub.
SVN:2.4[5274]
2018-01-17 10:22:14 +00:00
Denis Flaven
4ee6e6f915 (Retrofit from trunk) Fixed regression introduced by [r5235]: some directories (like data/production-modules) may not always exist... this should not stop the setup.
SVN:2.4[5273]
2018-01-17 10:16:42 +00:00
Denis Flaven
e5b318b94f (Retrofit from trunk) Handle extensions with missing dependencies.
SVN:2.4[5272]
2018-01-17 10:15:11 +00:00
Denis Flaven
c081b89f03 (Retrofit from trunk) Small setup refactoring for getting ready for the Hub.
SVN:2.4[5271]
2018-01-17 10:13:14 +00:00
Pierre Goiffon
f237a98c1d (Retrofit from trunk) Exclude for itop-hub-connector (r5268)
SVN:2.4[5269]
2018-01-17 09:03:59 +00:00
Guillaume Lajarige
fd2a41aee6 (Retrofit from trunk) Portal: Fix CSS for selected rows in dataTables tables
SVN:2.4[5267]
2018-01-16 17:00:47 +00:00
Guillaume Lajarige
25b1a0d8af (Retrofit from trunk) N°984 Portal: Fix autocomplete field reset when changing value of parent field in request templates.
SVN:2.4[5262]
2018-01-16 15:39:44 +00:00
Eric Espié
985b366cc7 Retrofit from trunk
N°1246 - Fix Obsolete data visible in dependency graph.
* Fix a wrong transient OQL expression cache introduced in [4943]. [from revision 5257]

SVN:2.4[5259]
2018-01-16 15:05:27 +00:00
Guillaume Lajarige
d1e2bc9b2b (Retrofit from trunk) N°1276 Portal: Aligned drop-down list to autocomplete threshold behavior to console's behavior.
SVN:2.4[5256]
2018-01-16 14:27:01 +00:00
Eric Espié
757b3d1cc3 Retrofit from trunk
N°1224 - The 2.4.x setup keep the selected choices from a 1.3.x version.
* The selection is kept even if the extension has a one more module than the 1.3.x [from revision 5252]

SVN:2.4[5253]
2018-01-16 10:41:47 +00:00
Pierre Goiffon
8c0bbadbfe (Retrofit from trunk) Split the method to get a module absolute URL into 3 different methods, to allow more flexibility (for example get the URL and the query string key/value array to construct a GET form) (r5071, r5074, r5077)
SVN:2.4[5251]
2018-01-16 09:11:23 +00:00
Eric Espié
cbd3d2c165 Retrofit from trunk
N°1026 - Portal requests are too slow
* Counts on union requests are more optimized
* Requests for combo box values are more optimized [from revision 5249]

SVN:2.4[5250]
2018-01-16 08:46:49 +00:00
Eric Espié
b286bd48c7 Retrofit from trunk
N°870 - Avoid Obsolete data export in CSV, Excel and PDF [from revision 5218]
N°870 - Avoid Obsolete data in audit results [from revision 5219]
N°870 - Fix the display of archived objects in the dashlets when activating/deactivating the archive mode. [from revision 5226]

SVN:2.4[5248]
2018-01-15 08:17:24 +00:00
Pierre Goiffon
c4efbe2891 (Retrofit from trunk) N°942 set nex itop release MySQL requirement to 5.5.3 for utf8mb4 (r5245)
SVN:2.4[5246]
2018-01-12 16:08:22 +00:00
Pierre Goiffon
58506441cf (Retrofit from trunk) N°942 next itop release PHP & MySQL requirements : use *.0 versions instead of the latests (r5243)
SVN:2.4[5244]
2018-01-12 15:36:25 +00:00
Guillaume Lajarige
d575ee50ee (Retrofit from trunk) N°1199 Fixed "Notice: undefined index 0" in the portal. UserRequest/Incident::ComputePriority() was failing when attributes impact had no value.
SVN:2.4[5241]
2018-01-12 13:57:21 +00:00
Pierre Goiffon
997fa3a856 (Retrofit from trunk) N°942 change next itop release MySQL version requirement from 5.6 to 5.5, plus add some comments (r5238)
SVN:2.4[5239]
2018-01-12 11:27:37 +00:00
Guillaume Lajarige
70abd8027e (Retrofit from trunk) Merged revision(s) 5192 & 5193.
r5192: N°1243 Fix MUST_CHANGE/MANDATORY checks on transition in the console on an HTML Attribute.
r5193: Rollback modifications from r5192 as it introduced a regression.

JS escaping and previous value comparison strategies are to be define before going further with this matter.

SVN:2.4[5236]
2018-01-11 14:57:41 +00:00
Eric Espié
4c6e6ffc9c Retrofit from trunk
N°1191 - Wrong file name for backup check. [from revision 5143]
N°1188 - Backup needs too much disk space [from revision 5144]
N°1190 - Better error reporting and disk cleanup. [from revision 5145]
License reformat. [from revision 5146]
N°1188 & N°1189 Too much disk space / memory used for backup / restore - completing the commit [r5144], because the PHP extension phar is no more required [from revision 5151]

SVN:2.4[5233]
2018-01-11 08:05:10 +00:00
Romain Quetiez
b268df7bb4 (Retrofit from trunk) N°1147 Enable data synchronization for applications classes (Localized Data)
SVN:2.4[5231]
2018-01-10 14:20:18 +00:00
Eric Espié
7aaa35f88e Retrofit from trunk
N°925 - Fix portal when request template field is in autocomplete mode with a wrong value
* No error is displayed, but the actual value is set to '0' [from revision 5215]

SVN:2.4[5227]
2018-01-10 13:45:04 +00:00
Guillaume Lajarige
4bb91b819c (Retrofit from trunk) Portal: Change Ticket->public_log's flags in ev_reopen form. Now MUST_CHANGE instead of MUST_PROMPT.
SVN:2.4[5225]
2018-01-10 13:32:43 +00:00
Guillaume Lajarige
656cae3e66 (Retrofit from trunk) N°1157 Portal: Exception raised in BrowseBrick when one of the levels had no scope.
SVN:2.4[5223]
2018-01-10 10:45:25 +00:00
Guillaume Lajarige
292735b4b2 (Retrofit from trunk) N°1247 Fix AttributeEnum display as vertical radio buttons in console UI.
SVN:2.4[5217]
2018-01-09 14:44:37 +00:00
Pierre Goiffon
dcc4061261 (Retrofit from trunk) N°942 setup : max version for PHP (r5213)
SVN:2.4[5214]
2018-01-08 15:41:25 +00:00
Guillaume Lajarige
ff7ac731df (Retrofit from trunk) Fix regression introduced in r5183.
SVN:2.4[5212]
2018-01-08 12:41:52 +00:00
Guillaume Lajarige
c784afdb06 (Retrofit from trunk) N°1254 Portal: Add CSS/JS hooks on object forms for the current state
- CSS class on <form> tag: form_object_state_<STATE_CODE>
- HTML attribute on <form> tag: data-object-state="<STATE_CODE>"

SVN:2.4[5210]
2018-01-08 12:12:26 +00:00
Guillaume Lajarige
5e60e8d725 (Retrofit from trunk) N°1172.3 Portal: Objects and external keys in linkedsets (forms) now open in a modal dialog.
SVN:2.4[5208]
2018-01-08 11:39:18 +00:00
Pierre Goiffon
564bf04647 (Retrofit from trunk) N°801 allow block quotes in HTML Fields
add BLOCKQUOTE tag in the HTMLDOMSanitizer white list (r5202)

SVN:2.4[5203]
2018-01-04 17:19:06 +00:00
Guillaume Lajarige
17d18eb75f (Retofit from trunk) N°1194 Portal: Support for MUST_CHANGE flag on CaseLog attributes in transitions.
SVN:2.4[5200]
2018-01-04 10:42:37 +00:00
Guillaume Lajarige
a9a6460747 (Retrofit from trunk) N°1245 Fix MUST_CHANGE flag behavior on CaseLog attributes in the console.
SVN:2.4[5198]
2018-01-03 14:53:11 +00:00
Guillaume Lajarige
1fa1a053e4 (Retrofit from trunk) N°1172.2 Fix regression introduced in r5161 (Email notification crash because of portal urls)
SVN:2.4[5196]
2018-01-03 09:46:49 +00:00
Guillaume Lajarige
01e151632f (Retrofit from trunk) N°1217.2 Console UI: Small enhancements on object properties display.
* HTML Attribute value not breaking on words anymore.
* Attribute label width bigger on single column display.

SVN:2.4[5195]
2018-01-03 09:44:19 +00:00
Pierre Goiffon
c17d0b1027 (Retrofit from trunk) N°942 setup : add checks for next iTop release requirements on PHP and MySQL versions (r5190)
SVN:2.4[5191]
2018-01-02 16:08:37 +00:00
Eric Espié
517d16e3d9 Retrofit from trunk
N°1070: Enhance ergonomics of "Add To Dashboard..." popup window
* Larger window to avoid the scrollbar.
* Check the dashboards root parent access rights to generate the dashboards list proposed in the popup.
* Select by default the first dashboard of the current menu.
[from revision 5137, 5185]

SVN:2.4[5186]
2018-01-02 13:33:20 +00:00
Guillaume Lajarige
696a5d1a48 (Retrofit from trunk) N°1227 New configuration parameter (disable_attachments_download_legacy_portal) to disable attachments download from the legacy portal. Default is "true"!
SVN:2.4[5184]
2017-12-29 13:59:41 +00:00
Guillaume Lajarige
96288db97b (Retrofit from trunk) N°1132 Add ContextTag on CRON background tasks (eg. "CRON:Task:<CLASS_NAME_OF_THE_CURRENT_TASK>").
Introduced for the "Mail to ticket automation" extension, so we know when a Ticket is created/updated from an email.

SVN:2.4[5182]
2017-12-29 09:59:37 +00:00
Guillaume Lajarige
e6072cd8b6 (Retrofit from trunk) N°1143 Fix removed email links (mailto) in HTML attributes (CKEditor).
SVN:2.4[5180]
2017-12-28 15:37:57 +00:00
Guillaume Lajarige
db8bb7d94a (Retrofit from trunk) N°850 Show "delete" and "bulk delete" rights in user's grant matrix.
SVN:2.4[5178]
2017-12-28 10:38:26 +00:00
Guillaume Lajarige
1b2dbf751c (Retrofit from trunk) N°624 Fix WYSIWYG feature in CaseLog / HTML attributes on transition.
SVN:2.4[5176]
2017-12-28 09:27:15 +00:00
Guillaume Lajarige
b6805af20e (Retrofit from trunk) N°1172 Portal: Objects and external keys in linkedsets (forms) now have hyperlinks if access is authorized regarding the user's scopes.
SVN:2.4[5174]
2017-12-27 16:12:15 +00:00
Guillaume Lajarige
4cc6290c88 (Retrofit from trunk) Compiled CSS filed from previous commit (r5168)
SVN:2.4[5173]
2017-12-27 16:10:18 +00:00
Guillaume Lajarige
c79d17516d (Retrofit from trunk) N°1217 Console UI improvements in object forms.
- Columns size optimization.
- Tooltip on (none empty) String attribute so long value can be seen without scrolling to the end of the input.
- OQL attribute displayed as Text/HTML attributes.

SVN:2.4[5172]
2017-12-27 16:09:25 +00:00
Pierre Goiffon
a2154d6418 (Retrofit from trunk) N°1182 fix overlapping table in console dashlets : now we have a scrolling bar if necessary [from revision 5168]
SVN:2.4[5169]
2017-12-27 14:53:54 +00:00
Guillaume Lajarige
f820d3bd81 (Retrofit from trunk) N°916 Fix impact analysis relation upstream description.
Description was unique for both directions. Now 2 separate entries are used 'Realtion:<RELATION_CODE>/<DIRECTION>Stream+'.
Translations for existing languages are already done.

NOTE: pages/UI.php a été repassé en fin de lignes Unix (LF) car il était pour une raison inconnue en Windows (CRLF). De fait, les annotations ne marcheront plus sur ce fichier dans la branche 2.4, mais elles sont toujours dispos sur trunk.

SVN:2.4[5167]
2017-12-27 10:13:27 +00:00
Eric Espié
0a914f527a Retrofit from trunk
N°789 - Fix losing the additional links attributes values during impact analysis update [from revision 5162]

SVN:2.4[5163]
2017-12-22 13:22:50 +00:00
Denis Flaven
a8c749a46a (retrofit from trunk) Preparing for integration with the Hub.
SVN:2.4[5159]
2017-12-15 14:55:25 +00:00
Eric Espié
e58c8afeab Retrofit from trunk
N°1209 - Fix Organization selector width (missing button) [from revision 5157]

SVN:2.4[5158]
2017-12-15 13:19:18 +00:00
Eric Espié
005b0eab1d Retrofit from trunk
N°1209 - Fix Organization selector width (padding added) [from revision 5154-5155]

SVN:2.4[5156]
2017-12-15 13:06:50 +00:00
Denis Flaven
55b34b5d11 (retrofit from trunk) Enhancement: automatically recognize some well-know multi-module extensions deployed using the old format (i.e. shipped without an extension.xml file) and emulate the new format for them in order to display a meaningful label and version in the setup and in the about box.
SVN:2.4[5153]
2017-12-14 12:14:08 +00:00
Eric Espié
7b4f00ee45 Retrofit from trunk
N°1163 - GET_LOCK 64 characters limitation in MySQL [from revision 5135]

SVN:2.4[5136]
2017-11-21 12:32:13 +00:00
Romain Quetiez
cc99cec250 (Retrofit from trunk) N°1160 - Fix error when sending notification with list of links.
SVN:2.4[5133]
2017-11-17 16:37:56 +00:00
Eric Espié
105dc46f46 Retrofit from trunk
N°1156 - Manual backup can be very long
* The database is saved in last position to avoid overhead when generating the archive file [from revision 5130]

SVN:2.4[5131]
2017-11-16 14:05:10 +00:00
Vincent Dumas
770fbe157c dictionnary typo on Notification header message
SVN:2.4[5127]
2017-11-03 09:30:15 +00:00
Denis Flaven
21d44c664f (Regression - retrofit from trunk) Fix display trouble for auto_reload menus. This was caused by a collision of HTML/DOM ids where the menu item of the left (accordion) menu had the same id as the div displaying the actual content in the right pane. This caused (when the id was a valid one !) the refresh of the content (list of objects) to occur INSIDE the accordion menu !
SVN:2.4[5122]
2017-10-31 10:46:09 +00:00
Guillaume Lajarige
b318cbe211 (Retrofit from trunk) N°634.3 Portal: Argh!! Secondary actions menu in BrowseBrick was broken due to previous CSS "fixes"...
SVN:2.4[5120]
2017-10-30 16:05:26 +00:00
Romain Quetiez
7106501812 (Retrofit from trunk) Ready for releasing...
SVN:2.4[5118]
2017-10-30 14:27:56 +00:00
Eric Espié
567dd39270 Retrofit from trunk
2.4.0 - New readme format. [from revision 5115]

SVN:2.4[5116]
2017-10-30 14:16:11 +00:00
Eric Espié
b292621f82 Retrofit from trunk
Core russian translation kindly provided by Vladimir Kunin. [from revision 5113]

SVN:2.4[5114]
2017-10-30 11:38:21 +00:00
Guillaume Lajarige
46f0b48add (Retrofit from trunk on behalf of sabello) added german translation for obsolescence. Thanks to ITOMIG.
SVN:2.4[5110]
2017-10-27 14:34:04 +00:00
Denis Flaven
9a5f16bd3e (retrofit from trunk) Simplification of the obsolescence condition (N°890) to avoid reaching the limit of 61 tables (N°1049)
SVN:2.4[5108]
2017-10-27 13:11:55 +00:00
Guillaume Lajarige
6224ec7a37 (Retrofit from trunk) Internal: Updated modules version to 2.4.0 (as well as some copyright dates)
SVN:2.4[5106]
2017-10-27 08:58:20 +00:00
Pierre Goiffon
c460edcd5e (Retrofit from trunk) r5093 Change version number for static resources calls
SVN:2.4[5101]
2017-10-27 07:55:07 +00:00
Pierre Goiffon
b839b42cd4 (Retrofit from trunk) r5091 Change modules XML version to the latest (1.4)
SVN:2.4[5100]
2017-10-27 07:54:58 +00:00
Pierre Goiffon
fabc8b91d7 (Retrofit from trunk) r5090 Change version number in CSS url() calls
SVN:2.4[5099]
2017-10-27 07:54:37 +00:00
Guillaume Lajarige
4c38cd570c (Retrofit from trunk) N°1138 Portal: Scrollbar appeared sometimes in navigation menu when on the last brick.
SVN:2.4[5098]
2017-10-26 17:41:28 +00:00
Guillaume Lajarige
c4fd15ae90 (Retrofit from trunk) N°930.2 Console UI: Better object details layout step 2.
SVN:2.4[5096]
2017-10-26 17:07:44 +00:00
Eric Espié
bbca491118 Retrofit from trunk
Fix utils::GetCurrentModuleUrl() bug introduced in revision 4920 [from revision 5085]

SVN:2.4[5086]
2017-10-25 16:01:16 +00:00
Guillaume Lajarige
0d04bd8b0e (Retrofit from trunk) Translations: Added icon attribute for ServiceFamily and Service classes. English and French done, feel free to contribute for others ! :)
SVN:2.4[5084]
2017-10-24 15:23:02 +00:00
Guillaume Lajarige
3bcb3a2154 (Retrofit from trunk) Internal: Updated sample data for Person, ServiceFamily and Service (use of AttributeImage)
SVN:2.4[5083]
2017-10-24 15:21:49 +00:00
Guillaume Lajarige
aa863c40d3 (Retrofit from trunk) N°634.2 Portal: CSS fixes on mosaic mode of BrowseBrick
SVN:2.4[5082]
2017-10-24 15:19:39 +00:00
Guillaume Lajarige
5d7ad9f6dd (Retrofit from trunk) Internal: Updated module version (itop-portal, itop-portal-base)
SVN:2.4[5081]
2017-10-24 15:18:05 +00:00
Eric Espié
72cb41dd7e Retrofit from trunk
N°1134 - Bad version number when MTP
* The order of the installed versions is changed when doing MTP. [from revision 5073]

SVN:2.4[5075]
2017-10-24 08:20:36 +00:00
Eric Espié
6ef39c9f5b Retrofit from trunk
N°1131 - Setup: Can't add options to ITIL Ticketing in update mode
* Fix the alternative selection when updating the setup
* Uncheck options when the alternatives are deselected [from revision 5067]

SVN:2.4[5068]
2017-10-23 11:40:46 +00:00
Eric Espié
614b948c82 Retrofit from trunk
N°1109 - itop-object-copier Create ticket from CI was not adding the CI in the CI list of the newly created ticket.
* Fix 'add_to_list' command.
* Fix Adding a n-n link at the creation time.

SVN:2.4[5064]
2017-10-20 14:16:54 +00:00
Eric Espié
7c0715ad59 Retrofit from trunk
N°1109 - itop-object-copier Create ticket from CI was not adding the CI in the CI list of the newly created ticket.
* Fix 'add_to_list' command.
* Fix Adding a n-n link at the creation time.

SVN:2.4[5063]
2017-10-20 14:13:12 +00:00
Guillaume Lajarige
31819497ae (Retrofit from trunk) N°1123.4 AttributeImage: Better UI when editing in console. Also, export was showing url for empty value.
SVN:2.4[5062]
2017-10-20 13:28:02 +00:00
Guillaume Lajarige
c86e2c6c79 (Retrofit from trunk) N°642.3 Portal: Lifecycle: Exception on UserRequest opening due to a bad variable initialization.
SVN:2.4[5059]
2017-10-20 12:43:21 +00:00
Eric Espié
1d54b66de9 Retrofit from trunk
N°1131 - Setup: Can't add options to ITIL Ticketing in update mode
* Fix the disable attribute of the options [from revision 5057]

SVN:2.4[5058]
2017-10-20 12:08:00 +00:00
Romain Quetiez
f932765e19 (Retrofit from trunk) N.1100 Regression introduced in [r4943], aka 2.4 RC3, and causing error during MTP (accessing the wrong expression cache)
SVN:2.4[5055]
2017-10-19 19:10:25 +00:00
Pierre Goiffon
11b4085566 (Retrofit from trunk) r5051 N.689 workaround on MySQL number of joins limit (61)
* change MySQLException to store initial exception code
* added a try/catch to launch query with full lazy load (no attr => only id)
* load finalClass field if needed (class is nor standalone nor a final leaf)

SVN:2.4[5052]
2017-10-19 13:58:39 +00:00
Guillaume Lajarige
12afcd7cce (Retrofit from trunk) N°1094 & N°1107 Portal: Mosaic mode in BrowseBrick displays icon nicely in IE9. Also, "name" on tiles doesn't break layout anymore when too long.
SVN:2.4[5049]
2017-10-19 13:02:24 +00:00
Guillaume Lajarige
666c95a656 (Retrofit from trunk) N°1071 UI: Better rendering for external keys in linkedset (no more wrapping)
SVN:2.4[5048]
2017-10-19 13:01:28 +00:00
Guillaume Lajarige
fde9c28263 (Retrofit from trunk) N°1129.1 Support of $this->hyperlink(itop-portal)$ in notifications (only "portal" was implemented for the default portal).
SVN:2.4[5047]
2017-10-19 13:00:36 +00:00
Guillaume Lajarige
52c3ff0406 (Retrofit from trunk) N°1094
- Adding "icon" attribute to ServiceFamily and Service classes
- Show icon on ServiceFamily and Service levels in BrowseBrick


SVN:2.4[5043]
2017-10-18 15:00:50 +00:00
Guillaume Lajarige
9f4d07aaa5 (Retrofit from trunk) N°1123.2 AttributeImage: default_image is no longer mandatory.
SVN:2.4[5041]
2017-10-18 14:53:36 +00:00
Guillaume Lajarige
70561d6331 (Retrofit from trunk) N°1123 AttributeImage: PHP notice when displaying an object without default_image on a AttributeImage attribute.
SVN:2.4[5039]
2017-10-18 14:44:19 +00:00
Eric Espié
ae8311e224 Retrofit from trunk
N.1065 Fix performance issues.
* Added the bGetCount flag into the cache to differentiate the cache entries for COUNT only. [from revision 5034]

SVN:2.4[5035]
2017-10-18 12:58:32 +00:00
Guillaume Lajarige
e09fa0ffc1 (Retrofit from trunk) N°911.2 Portal: Updated typeahead repository url in files headers.
SVN:2.4[5033]
2017-10-18 12:26:29 +00:00
Guillaume Lajarige
5b6a20048f (Retrofit from trunk) N°1122 Portal: Clean-up of 2 redundants JS files regarding the autocomplete inputs in forms.
SVN:2.4[5032]
2017-10-18 12:25:00 +00:00
Romain Quetiez
d86c211b73 (Retrofit from trunk) N.1108 return exception if $bMustBeFound and result is archived
SVN:2.4[5027]
2017-10-17 09:04:26 +00:00
Romain Quetiez
8499babcb3 (Retrofit from trunk) N°1092.1 Setup / MTP improvements regarding the environments folders:
- /env-production-build rights check before running setup
- /env-xxx-build is no longer deleted after MTT / MTP from the ITSM Designer. This prevents permissions issue when webserver user doesn't have suffisant rights on the root folder.


SVN:2.4[5026]
2017-10-17 08:02:30 +00:00
Romain Quetiez
2d44cbbb60 (Retrofit from trunk) Fixed dictionary typos (Chemin d'installation, En attente d'approbation)
SVN:2.4[5024]
2017-10-16 07:42:28 +00:00
Eric Espié
3d89ef9076 Merged from trunk
N.1065 Fix performance issues.
* Does not cache requests containing "id IN ..." and "id NOT IN ..." (too specific) [from revisions 5012, 5019]

SVN:2.4[5020]
2017-10-12 15:33:23 +00:00
Eric Espié
e896f8af3d (Retrofit from trunk)
N°1110 - DataSynchro: PHP Notice Undefined Index
* Fix access to REQUEST_URI when called by script [from revisions 5007, 5017]

SVN:2.4[5018]
2017-10-12 13:57:31 +00:00
Eric Espié
23268e8969 (Retrofit from trunk) Message when no data are available [from revision 5011]
SVN:2.4[5016]
2017-10-12 13:49:06 +00:00
Eric Espié
421e12debd (Retrofit from trunk)
N°870: Bulk operation and obsolescence flag
* Avoid the hidden selection (and update) of obsolete data when the user does not want to see the obsolete data. [from revision 4997]

SVN:2.4[5014]
2017-10-12 13:13:22 +00:00
Eric Espié
389848cef4 (Retrofit from trunk)
N.1065 Fix performance issues.
Cache display CSV format fix. [from revision 4996]

SVN:2.4[5013]
2017-10-12 13:10:50 +00:00
Guillaume Lajarige
7da4423346 (Retrofit from trunk) N°642.2 Portal: Lifecycle transitions security is now a blacklist instead of a white list. Making migration transparent and portal configuration easier.
SVN:2.4[5010]
2017-10-12 08:55:01 +00:00
Guillaume Lajarige
2a770b9dc4 (Retrofit from trunk) N°1107.1 Portal: Fixed image display in mosaic mode of BrowseBrick for Chrome and Firefox. IE still pending !
SVN:2.4[5009]
2017-10-12 08:54:10 +00:00
Pierre Goiffon
8df12e64f2 (Retrofit from trunk) r4492 N.1065 Fix performance issues.
Add statistics on query table join optimization.

SVN:2.4[4995]
2017-10-06 14:25:49 +00:00
Romain Quetiez
341261c14e (Retrofit from trunk) Fixed integration issue (possibly a regression): if an extension implements iApplicationExtension::OnDBInsert, and it calls DBWrite, then a fatal error occurs (call a member function on a null value). The error occurs for several types of attributes such as ormCustomField, ormCaseLog, ormLinkSet. The fix consists in aligning the internal values of a DBObject as soon as it has been written into the Database.
SVN:2.4[4994]
2017-10-06 14:07:33 +00:00
Guillaume Lajarige
bfc7c73e18 (Retrofit from trunk) Standard DataModel: Add org_id and location_id to 'Rack' reconciliation keys. Mandatory for CSV import of CI on a Rack, when Rack name is not a unique identifier.
SVN:2.4[4990]
2017-10-06 08:09:31 +00:00
Guillaume Lajarige
9a6c4ba7bb (Retrofit from trunk on behalf of cisou) FR dictionnary typo for obsolescence
SVN:2.4[4989]
2017-10-06 08:06:23 +00:00
Pierre Goiffon
dc47c34981 (Retrofit from trunk) r4983 N.1065 When joining, reverse leaf-root order : now it's root first !
* decrease the amount of joins on obsolescence use cases
* should also improve other uses cases as most of the time we believe the attribute linked is in the root class !
* the root table join is done using expressions instead of OQL for perf reasons
* a where clause on finalclass is also added to avoid problems if the leaf table join is not used (would be removed during query optimization phase)

SVN:2.4[4988]
2017-10-05 16:03:08 +00:00
Eric Espié
3ac5131a19 (Retrofit from trunk) r4969
N°1098 Fix CSV import by id. Fix a regression introduced in rev 4885.
---------------------
r4968
N°1100 - External field pointing to a magic attribute * A specific pass has been added in MetaModel::InitClasses() to generate the magic attributes before the external fields.
---------------------

SVN:2.4[4987]
2017-10-05 16:00:49 +00:00
Eric Espié
134a9aa684 (Retrofir from trunk) N.1065 Fix performance issues (unexpected objects reload).
* Fix regression in 2.4 into attribute optimization (archive_flag, obsolescence_flag).
* Fix attribute optimization (friendlyname for ExternalField pointing to ExternalKey)

SVN:2.4[4986]
2017-10-05 15:59:21 +00:00
Eric Espié
a98c0d32ae (Retrofit from trunk)
r4961
N°1065 Fixed a regression introduced in r4965.


SVN:2.4[4985]
2017-10-05 15:57:16 +00:00
Eric Espié
38af2b85c4 (Retrofit from trunk)
r4961
N°1065 Fixed a regression introduced in r4965.
---------------------
r4960
N.1065 Fix performance issues. Limit APC emulation cache entries to avoid disk saturation. New configuration entry added: 'apc_cache_emulation.max_entries'.
---------------------
r4956
N.1065 Fix performance issues. ormLinkSet creates the objects on demand.
---------------------
r4954
APC emulation using files when APC or APCu is not installed.

SVN:2.4[4984]
2017-10-05 15:55:15 +00:00
Guillaume Lajarige
c9c84735c4 (Retrofit from trunk) N°1025 Portal: Fixed regression introduced in r4863.
SVN:2.4[4982]
2017-10-05 15:52:12 +00:00
Guillaume Lajarige
646972838a (retrofit from trunk) N°1034 New EventOnObject class to store explaination on object's updates
SVN:2.4[4981]
2017-10-05 15:50:34 +00:00
Guillaume Lajarige
ce92241593 (Retrofit from trunk) N°653.2 Lifecycle: Fixed a regression introduced on r4767, transition buttons not working properly when editing an object.
SVN:2.4[4980]
2017-10-05 15:49:36 +00:00
Guillaume Lajarige
715eeff627 (Retrofit from trunk) N°1082 Fixed a regression introduced with ormLinkSet rework: Modified links not updated.
SVN:2.4[4979]
2017-10-05 15:48:13 +00:00
Romain Quetiez
651de821df (Retrofit from trunk) N.1065 and #372 Query build cache not efficient with global search (each search generates about 80 new entries in the APCu cache)
SVN:2.4[4978]
2017-10-05 15:46:53 +00:00
Guillaume Lajarige
2233ea5f54 (Retrofit from trunk) N°1067 Rework on ormLinkSet BC with DBObjectSet.
- PHP notice are not thrown anymore, see PHPDoc instead.
- GetColumnAsArray() introduced.

SVN:2.4[4977]
2017-10-05 15:46:21 +00:00
Eric Espié
39e6915e05 r4945
(Retrofit from trunk) Fix regression introduced in trunk[4943]
---------------------
r4944
Code cleanup.
---------------------
r4943
N.1065 Fix performance issues (caches added on query build)

SVN:2.4[4976]
2017-10-05 15:45:35 +00:00
Guillaume Lajarige
6279f4ac70 (Retrofit from trunk) Internal: Typo in PHPDoc
SVN:2.4[4975]
2017-10-05 15:45:01 +00:00
Guillaume Lajarige
badf10e74e (Retrofit from trunk) N°1073 Reentrance issue on cmdbAbstractObject when coming from an extension implementing iApplicationObjectExtension.
SVN:2.4[4974]
2017-10-05 15:43:50 +00:00
Guillaume Lajarige
2e9cd6a342 (Retrofit from trunk) Internal: Changed way AttributeLinkedSet check if two ormLinkSet are equal.
SVN:2.4[4973]
2017-10-05 15:42:51 +00:00
Guillaume Lajarige
cef70ee9a2 (Retrofit from trunk) Internal: Typo in itop-tickets XML comments
SVN:2.4[4972]
2017-10-05 15:41:52 +00:00
Eric Espié
37e15706b2 (Retrofit from trunk) Fix utils::GetCurrentModuleUrl() introduced in revision 4844
SVN:2.4[4970]
2017-10-05 15:34:09 +00:00
Denis Flaven
4b210273d2 (retrofit from trunk) N°1806, N°1069: CSV and Excel export and import of documents (files) and images as URLs.
SVN:2.4[4953]
2017-09-30 09:24:29 +00:00
Romain Quetiez
951e8e03b0 (Retrofit from trunk] N.1072 Localization for magic attributes archive_date (completed the existing implementation) and obsolescence_date (full implementation)
SVN:2.4[4947]
2017-09-27 12:38:01 +00:00
Denis Flaven
c016ca364d Internal: Make sure that UI dialogs are never bigger than the browser's window (not used in iTop though).
SVN:2.4[4941]
2017-09-26 10:00:24 +00:00
Romain Quetiez
4611442665 (Retrofit from trunk) N.707 Export of custom fields: improved the xlsx format and implemented the spreadsheet format (both are aligned)
SVN:2.4[4936]
2017-09-22 13:52:25 +00:00
Denis Flaven
c7e7976607 Added the open source logo as a character (uppercase letter O) to the font.
Increased the version number in the CSS to prevent caching/refresh issues.

SVN:2.4[4931]
2017-09-21 14:29:25 +00:00
Romain Quetiez
0ca9fac04b (Retrofit from trunk) N°813 Enable bulk deletion of Data Synchro Replica
SVN:2.4[4928]
2017-09-21 10:13:02 +00:00
Romain Quetiez
7edc79f398 (Retrofit from trunk) Code robustness: Though the commit [4922] solves the issue N.1052, it is safer to reset the cache as part of the "Commit" performed during the installation process.
SVN:2.4[4925]
2017-09-20 14:53:41 +00:00
Romain Quetiez
c110206264 (Retrofit from trunk) N.1052 After a setup or MTP, the datamodel is not taken into account... until the web server gets restarted or the APC cache (user data) gets reset.
SVN:2.4[4924]
2017-09-20 14:48:07 +00:00
Guillaume Lajarige
b3ad2030cb (Retrofit from trunk) N°1006.2 Templates: $this->raw(attcode)$ can be used to display an date(time) attribute in the SQL format like before.
SVN:2.4[4919]
2017-09-18 15:56:08 +00:00
Guillaume Lajarige
0938ba939c (Retrofit from trunk) N°1006 Templates: Date & time format is now applied when using a date(time) attribute in a placeholder (eg. Notifications)
SVN:2.4[4918]
2017-09-18 15:55:02 +00:00
Guillaume Lajarige
bca393def3 (Retrofit from trunk) N°1019 Portal: OpenSans font embedded in iTop instead of fetching from google servers.
SVN:2.4[4917]
2017-09-18 15:53:31 +00:00
Guillaume Lajarige
796ac55ff4 (Retrofit from trunk) N°762 Portal: Pre-filtering a browse brick in tree mode was making tree collapsing instead of showing results.
SVN:2.4[4916]
2017-09-18 15:43:19 +00:00
Guillaume Lajarige
66ae89e91d (Retrofit from trunk) N°1038 Fatal error on transition with AttributeBlob or AttributeCaseLog
SVN:2.4[4908]
2017-09-13 16:09:02 +00:00
Denis Flaven
361cca465a (retrofit from trunk) Added E and F glyphs.
SVN:2.4[4906]
2017-09-13 08:55:24 +00:00
Denis Flaven
7462d66643 Combodo font enhancements.
SVN:2.4[4904]
2017-09-12 14:49:46 +00:00
Guillaume Lajarige
a177b9b1d4 iTop 2.4: Code freeze
SVN:2.4[4902]
2017-09-08 14:25:46 +00:00
Guillaume Lajarige
48f6635917 N°1029 Allowed portals are now displayed in the console user menu.
SVN:trunk[4901]
2017-09-08 09:39:31 +00:00
Guillaume Lajarige
d6707743a9 N°1024 Portal: New portal instances can now be XML only (through ITSM Designer), no more need for a dedicated module !
SVN:trunk[4900]
2017-09-07 16:03:55 +00:00
Guillaume Lajarige
6f474686ad N°1027 Internal
SVN:trunk[4899]
2017-09-07 16:01:02 +00:00
Guillaume Lajarige
affd8ea8a6 Internal: Code cleanup. Unecessary var_dump as it is already logged in the error log.
SVN:trunk[4898]
2017-09-06 18:56:08 +00:00
Romain Quetiez
abd2748a09 N.890.1 Move to test/prod failing with message "Unknown attribute org_id_archive_flag from class contact"
SVN:trunk[4897]
2017-09-06 15:31:42 +00:00
Guillaume Lajarige
6a1c9b9f26 N°1021 Regression introduced in r4841: Autocomplete failing with "Unknown filter code..."
SVN:trunk[4896]
2017-09-06 08:01:45 +00:00
Romain Quetiez
ac04aa3359 N.981 Regression introduced in [r4692]. It make sense to preserve the existing value... excepted when reloading some form elements (filtering depending on fields that have just been touched by the user).
SVN:trunk[4895]
2017-09-05 15:44:48 +00:00
Guillaume Lajarige
051d68c902 Internal: Completed documentation in itop-tickets datamodel.
SVN:trunk[4894]
2017-09-05 12:14:42 +00:00
Guillaume Lajarige
ce81f7abb6 Portal: Filter was not looking in the item description in tree mode.
SVN:trunk[4893]
2017-09-05 12:14:00 +00:00
Guillaume Lajarige
03fbde3403 N°769.1 Portal: Renamed "count per page" variable in bricks in order to prepare refactoring for bug N°769.
SVN:trunk[4892]
2017-09-04 15:56:18 +00:00
Guillaume Lajarige
59ddab9f94 N°781 Setup: New hook available after data load (ModuleInstaller::AfterDataLoad())
SVN:trunk[4891]
2017-09-04 10:46:33 +00:00
Denis Flaven
225cbb450e N°1012: The method "Set" cannot be used to initialize a Blob or an Image with a text string value. Do not let the user think it may work.
SVN:trunk[4890]
2017-09-04 10:33:54 +00:00
Romain Quetiez
7cace73844 N.1014 Fixed regressions introduced in the rework of MTP/MTT : test database named after test-build, and losing the data/delta.<nev>.xml file, and cleanup badly implemented
SVN:trunk[4889]
2017-09-01 14:44:28 +00:00
Vincent Dumas
2562d33997 German Dictionnaries for iTop version 1.4 from Itomig
SVN:trunk[4888]
2017-09-01 08:33:35 +00:00
Guillaume Lajarige
a1e5b32e49 N°976 PHP Warning on not initialized variable $sHTMLValue in cmdbAbstractObject::GetFormElementForField().
SVN:trunk[4887]
2017-08-31 14:52:44 +00:00
Guillaume Lajarige
0c5df83a95 N°817 Portal: Linkset widget opening was throwing a warning message on IE9.
SVN:trunk[4886]
2017-08-31 14:42:33 +00:00
Guillaume Lajarige
cf79456ff3 N°583.2 Fixed regression introduced in r4861.
SVN:trunk[4885]
2017-08-31 13:34:50 +00:00
Romain Quetiez
a0a378a91a N.948 - Obsolete objects become visible when navigating in paginated lists
SVN:trunk[4884]
2017-08-29 14:58:18 +00:00
Guillaume Lajarige
9cf42f6cc8 N°1002 Backup: '.' character in iTop instance path was causing a crash when trying to -temporary- replace them in the backup filename (due to compression lib limitations).
SVN:trunk[4883]
2017-08-29 14:30:46 +00:00
Denis Flaven
c022d12a0a N°409 - completely remove the use of Flash (via Open Flash Charts), and therefore remove the php-ofc library. iTop now uses c3.js and d3.js instead.
SVN:trunk[4882]
2017-08-29 10:07:27 +00:00
Romain Quetiez
3d72060bf5 N.890.1 and N.890.2: build a new run time environment into a separate "build" directory, then commit it by the mean of quick and bullet proof file copies/moves. Not yet used in the setup.
SVN:trunk[4881]
2017-08-29 08:08:24 +00:00
Guillaume Lajarige
43709576c0 N°554.3 Not displaying actions menu in n:n in read mode.
SVN:trunk[4880]
2017-08-28 10:13:53 +00:00
Guillaume Lajarige
8ca6610e75 N°858.2 Fixed regression introduced in r4716 making DataSynchro menu hidden from the Admin menu
SVN:trunk[4879]
2017-08-25 13:10:31 +00:00
Guillaume Lajarige
b02d347541 N°977 DataSynchro: Creation and edition was broken due to the new object set API from ormLinkSet.
Backward compatible method have been introduce to ensure plugins and modules compatibility. That being said they are already flagged as deprecated and should not be used.
New: Using those deprecated methods will raise a PHP deprecated error.

SVN:trunk[4878]
2017-08-25 13:06:21 +00:00
Guillaume Lajarige
a39789cf89 N°782.2 Linkset menu actions open in a new tab in edition mode only.
SVN:trunk[4877]
2017-08-25 09:36:16 +00:00
Guillaume Lajarige
58534dca39 N°782.1 New configuration parameter 'allow_menu_on_linkset' (boolean, default value false) to display actions in linkset in view mode (new, modify, delete, ...)
SVN:trunk[4876]
2017-08-25 08:35:59 +00:00
Guillaume Lajarige
538e9c11e8 N°779 Date format handling in LinkedSetIndirect was causing fatal error on object edition.
SVN:trunk[4875]
2017-08-24 13:24:46 +00:00
Romain Quetiez
cba6e8d8a3 890.3 Fixed regression introduced in commit 4870 - fatal error during a MTP from the designer
SVN:trunk[4874]
2017-08-23 15:31:08 +00:00
Guillaume Lajarige
fd9fe85ef3 N°844 Portal: ExternalField support in forms was not properly supporting external key as readonly.
SVN:trunk[4873]
2017-08-23 14:39:58 +00:00
Guillaume Lajarige
7ed3e10d0f N°978 Portal: Added cache delay to BrowseBrick item pictograms.
SVN:trunk[4872]
2017-08-22 12:34:12 +00:00
Guillaume Lajarige
fe98b850d1 N°978 Portal: Display / download of blob attributes and attachments in the portal was not compatible with portal configuration and silos by-passing.
SVN:trunk[4871]
2017-08-22 10:38:10 +00:00
Romain Quetiez
b1494d0dd9 N890.3 Archive mode toggle menu not visible after setup/MTP: the information is cached into the session and needs to be reset
SVN:trunk[4870]
2017-08-21 15:28:07 +00:00
Guillaume Lajarige
0655904683 N°849 Edition of an object with not allowed (silos) remote objects in a linkedset causes fatal error.
SVN:trunk[4869]
2017-08-21 10:12:19 +00:00
Guillaume Lajarige
bad5aff764 Internal: PHPDoc on some DBObjectSet methods.
SVN:trunk[4868]
2017-08-18 15:37:44 +00:00
Guillaume Lajarige
440f2639b5 N°498 Added some attributes to the HTML sanitizer (title for a tag, alt / title for img tag).
SVN:trunk[4867]
2017-08-18 13:59:49 +00:00
Guillaume Lajarige
5f2245595f N°313 Show product name on branding logo title instead of a generic "iTop" text.
SVN:trunk[4866]
2017-08-18 13:50:33 +00:00
Guillaume Lajarige
03db4e7a14 N°934 Hierarchy button loading indicator was not hidden on HK selection.
SVN:trunk[4865]
2017-08-18 12:55:59 +00:00
Guillaume Lajarige
04030e07e3 Internal: Removed PHP notices in ajax call from autocomplete fields in the console
SVN:trunk[4864]
2017-08-17 15:47:32 +00:00
Guillaume Lajarige
80832877eb N°984 Portal: Dependancies on autocomplete fields now works properly. (Changing value on a parent autcomplete was not resetting value on a dependant autocomplete field)
SVN:trunk[4863]
2017-08-17 15:21:17 +00:00
Guillaume Lajarige
aa63847502 N°991 Portal: Fixed a regression introduced in r4855 (N°743); request template selection was not working when several templates available.
SVN:trunk[4862]
2017-08-17 10:08:17 +00:00
Guillaume Lajarige
4171c5c35f N°583 CSV Import now supports friendlynames as reconciliation keys. More over, read-only attributes (friendlyname, obsolescence flag, ...) are forced to reconciliation scheme.
SVN:trunk[4861]
2017-08-17 09:51:38 +00:00
Guillaume Lajarige
8e06a95502 N°844 Portal: ExternalField support in forms has been improved (1 file missing from previous commit)
SVN:trunk[4860]
2017-08-16 09:41:21 +00:00
Guillaume Lajarige
1c84934777 N°649 Stop displaying Ticket objects in a CI's ongoing tickets tab when the impact code is 'not impacted'.
SVN:trunk[4859]
2017-08-11 09:52:38 +00:00
Guillaume Lajarige
0294f50339 N°410 Modal dialog's height for CSV export partially hidden because no scrollbar available.
SVN:trunk[4858]
2017-08-10 14:45:20 +00:00
Guillaume Lajarige
33098943a1 N°744 Portal: Prevented LinkedSet corruption through simultaneous updates. In the portal are now update incremantally like in the console. This needs to be tested with both 1:n and n:n LinkedSet
SVN:trunk[4857]
2017-08-10 13:55:41 +00:00
Guillaume Lajarige
b953a6ab3e N°377 Impact analysis: UI Glitch in tooltip when text was too long.
SVN:trunk[4856]
2017-08-09 15:09:35 +00:00
Guillaume Lajarige
7287cab6f4 N°743 Portal: Only editable fields are now passed in forms submit, fixing issue where a portal user could unwillingly change the UserRequest status if a Support Agent had assign the ticket while the portal user was editing.
SVN:trunk[4855]
2017-08-09 10:10:53 +00:00
Guillaume Lajarige
df4cad3ff1 N°844 Portal: ExternalField support in forms has been improved. For example, email and url links were not displayed as proper HTML.
SVN:trunk[4854]
2017-08-08 11:59:53 +00:00
Guillaume Lajarige
66b6206daf N°949 Cleaned up old datamodel (1.x) as it was no longer maintained and could not be upgraded.
SVN:trunk[4853]
2017-08-03 10:11:39 +00:00
Guillaume Lajarige
6f40459215 N°952 Portal: Added UI extension APIs similar to those used in the console (Experimental!)
SVN:trunk[4852]
2017-08-02 11:30:30 +00:00
Guillaume Lajarige
24430e630f N°969 Portal: ManageBrick tabs could show objects that were not supposed to be shown due to a bad OQL interpretation.
SVN:trunk[4851]
2017-08-01 14:14:32 +00:00
Guillaume Lajarige
3420db26a5 N°564 LoginWebPage title default value is now a dictionary entry ('UI:Login:Title'). Only english & french are available for now, feel free to contribute ! :)
SVN:trunk[4850]
2017-07-31 15:06:21 +00:00
Guillaume Lajarige
c27cafb02f N°642 Portal: Fixed bug in new stimulus form algorithm
SVN:trunk[4849]
2017-07-28 15:01:51 +00:00
Denis Flaven
09a2dbc185 Bug fix: for archive and obsolescence flags: the parent class may be a pure PHP class. Use ParentPersisteClass instead.
SVN:trunk[4848]
2017-07-28 12:02:03 +00:00
Denis Flaven
015c5b7a96 Bug fix: protect the setup from a fatal exception when encountering a module with no version number.
SVN:trunk[4847]
2017-07-26 15:46:57 +00:00
Denis Flaven
4710ae6eb7 No change (just a comment for better typing return values)
SVN:trunk[4846]
2017-07-26 08:21:36 +00:00
Denis Flaven
c6b98dd5c1 Bug fix: do NOT scan the whole "data" directory searching for extensions !!
SVN:trunk[4845]
2017-07-26 08:19:13 +00:00
Denis Flaven
61d3a4e48f (FAF for module developers): utilities to easily know the name/version/dir/URL of the current module and write module independent code !!
SVN:trunk[4844]
2017-07-21 13:55:22 +00:00
Denis Flaven
be4c1e2c1c Bug fix: prevent a crash of the setup when there is no "extensions" directory at all.
SVN:trunk[4843]
2017-07-20 13:37:07 +00:00
Denis Flaven
6f499dd36e N°954: don't use the same (re)naming scheme, or risk a "cannot redeclare class" error.
SVN:trunk[4842]
2017-07-20 13:32:46 +00:00
Guillaume Lajarige
03661158f1 N°934 Hierarchy button was no longer working when editing external key
SVN:trunk[4841]
2017-07-19 14:20:12 +00:00
Romain Quetiez
b9866008b2 Impact analysis: better error report if the data model defines redundancy on a unidirectional relation (make it easier to troubleshoot for consultants)
SVN:trunk[4840]
2017-07-19 12:53:38 +00:00
Guillaume Lajarige
2557d6e7d5 N°706 DBCollation config parameter is no longer forced during setup. It was for migrating iTop 2.0 but is now obsolete.
SVN:trunk[4839]
2017-07-18 13:41:51 +00:00
Guillaume Lajarige
79582b55a4 N°579: Creating an object with the [+] button on external key pointing to an abstract class.
SVN:trunk[4838]
2017-07-18 13:37:29 +00:00
Romain Quetiez
46b5293867 N.542, N.912 Finalized the API UserRights::Impersonate. This is an enabler for several enhancements.
SVN:trunk[4837]
2017-07-18 09:36:25 +00:00
Guillaume Lajarige
54ca6ad3d9 Portal: Improved debug informations on error page
SVN:trunk[4836]
2017-07-14 19:24:36 +00:00
Guillaume Lajarige
2ab8161182 N°911 Portal: Autocomplete fields were not showing all items when result count was below autocomplete display limit (eg. Showing only 2 elements out of 18 when display limit set to 20)
SVN:trunk[4835]
2017-07-13 11:39:36 +00:00
Romain Quetiez
3c7f5db5e7 N890.6 Obsolescence: if the expression defining the flag is evaluated to null, then consider the object as being NOT obsoleted. The workaround implemented in the reference datamodel has been removed (no need to use COALESCE anymore).
SVN:trunk[4834]
2017-07-13 09:36:57 +00:00
Vincent Dumas
e72bc65df8 Set Module id 2.4 and XML version 1.4
SVN:trunk[4833]
2017-07-12 09:19:24 +00:00
Denis Flaven
c039414adc Don't add a question mark at the end of the setup's Iframe URL.
SVN:trunk[4832]
2017-07-12 08:51:34 +00:00
Romain Quetiez
d45cbb36f7 Regression introduced in the rework of N-N link edition. Parsing error on PHP 5.3
SVN:trunk[4831]
2017-07-11 15:11:46 +00:00
Romain Quetiez
ba5dd94240 Fixed a regression introduced by the new layout of forms in the console : notices issued on the transition form
SVN:trunk[4830]
2017-07-11 09:43:33 +00:00
Denis Flaven
fe84cbd978 Remove (once more...) a warning at the end of the setup...
SVN:trunk[4829]
2017-07-11 09:33:43 +00:00
Romain Quetiez
f8ddcc7b70 Releasing 2.4.0-beta
SVN:trunk[4828]
2017-07-11 08:27:51 +00:00
Vincent Dumas
c0d7737872 ReleaseNotes: rephrasing
SVN:trunk[4827]
2017-07-11 08:10:07 +00:00
Guillaume Lajarige
97e6c53add Portal: Some fields were no longer mandatory due to a regression introduced by the flags on lifecycle
SVN:trunk[4826]
2017-07-10 15:48:50 +00:00
Vincent Dumas
68ac5d7300 DataModel: Remove Ticket Archiving by default
SVN:trunk[4825]
2017-07-10 13:44:09 +00:00
Vincent Dumas
e0eebc0b4e ReleaseNote: 2.4. beta
SVN:trunk[4824]
2017-07-10 13:43:18 +00:00
Denis Flaven
fb28de971c Remove a warning at the end of the setup...
SVN:trunk[4823]
2017-07-10 13:23:03 +00:00
Guillaume Lajarige
ae637c7c96 Console UI: Fixed glitch in object details layout
SVN:trunk[4822]
2017-07-10 10:01:03 +00:00
Denis Flaven
6bc24b6064 File names are now purely reltive to the module's root directory.
SVN:trunk[4821]
2017-07-10 09:49:50 +00:00
Guillaume Lajarige
db60dfb707 Console UI: Text fields validation fixed when coming back from fullscreen. Also, fixed seaarch dialog UI for ExtKeys
SVN:trunk[4820]
2017-07-10 09:44:31 +00:00
Vincent Dumas
ee2aa67959 DataModel: Obsolescence conditions, workaround a code issue by using COALESCE when an obsolescence condition can be NULL.
SVN:trunk[4819]
2017-07-10 07:59:53 +00:00
Guillaume Lajarige
eff42f5d3b Console UI: OneWayPassword attribute are now displayed properly according to the new layout
SVN:trunk[4818]
2017-07-10 07:11:06 +00:00
Guillaume Lajarige
3a1810bc42 Console UI: Calendar icon for date(time) fields is now bigger.
SVN:trunk[4817]
2017-07-09 16:32:15 +00:00
Guillaume Lajarige
1f1ab2b10c N°930 Better UI on object details part 2: Text type fields now handled properly and aligned with HTML type fields.
SVN:trunk[4816]
2017-07-09 13:44:42 +00:00
Denis Flaven
d0d9b1ce50 Improved way to track the choices made during the installation in order to:
1) Be able to proerly report this information
2) Make sure that the same (proper) choices are proposed upon update

SVN:trunk[4815]
2017-07-07 16:00:30 +00:00
Guillaume Lajarige
307145502c N°930 Better UI on object details part 1. (Careful, Bulf modify and CSV import might be partly broken...)
SVN:trunk[4814]
2017-07-07 15:32:50 +00:00
Denis Flaven
5c84703cf0 Adding identifiers into the installation wizard.
SVN:trunk[4813]
2017-07-07 15:30:20 +00:00
Denis Flaven
cb2745be24 Bumping the datamodel version number to 2.4.0 !!
SVN:trunk[4812]
2017-07-07 15:28:42 +00:00
Denis Flaven
b613b9b302 Cosmetics on the asychronous DB connection message.
SVN:trunk[4811]
2017-07-07 15:27:32 +00:00
Denis Flaven
624f6bcfc5 Added a "Set" method.
SVN:trunk[4810]
2017-07-07 15:26:31 +00:00
Denis Flaven
ce2f1edaac Identify the menu nodes by an ID for potential CSS styling.
SVN:trunk[4809]
2017-07-07 15:24:14 +00:00
Denis Flaven
140efb4240 Split of a too long code line !!
SVN:trunk[4808]
2017-07-07 15:23:20 +00:00
Romain Quetiez
e3847ac24b Archive: Experimental API DBSearch::DBBulkWriteArchiveFlag, to quickly archive a huge number of objects (minimizes the number of queries needed to do the job, skips object change handlers, and DOES NOT RECORD the change in the history of object changes -that limitation could be fixed later)
SVN:trunk[4807]
2017-07-07 14:12:19 +00:00
Romain Quetiez
b583bd2edc N.612 Regression introduced in the previous commit: on the setup conclusion page, the link to download the backup file does not work (and warning "unknown variable $sTruncatedFilePath')
SVN:trunk[4806]
2017-07-07 09:30:52 +00:00
Vincent Dumas
c654a2067d Dictionnary: English: Labels on Modify AttributeDuration, aligned to display mode
SVN:trunk[4805]
2017-07-07 09:07:55 +00:00
Guillaume Lajarige
3531d5c5b7 Removed JS console.log usages that would have made IE9 crash
SVN:trunk[4804]
2017-07-06 15:55:02 +00:00
Romain Quetiez
e31fa066fc N.612 Backup files could not exceed 4Gb (technology limitation). The fix consists in archiving the backup as a tar.gz instead of a zip. As a consequence, installing iTop now requires TWO additional PHP modules: phar/zlib. The zip module remains mandatory because it is used in other places. The restore utility accepts both legacy zip files and brand new tar.gz files. DBBackup::CreateZip is deprecated in favor of DBBackup::CreateCompressedBackup. DBRestore::RestoreFromZip is deprecated in favor of DBRestore::RestoreFromCompressedFile (which autodetects the format for backward compatibility).
SVN:trunk[4803]
2017-07-06 15:26:03 +00:00
Vincent Dumas
bc476295cb DataModel: obsolescence conditions: Added: Contact, Location, Org. Modified: Hypervisor, Ticket
SVN:trunk[4802]
2017-07-06 14:54:20 +00:00
Vincent Dumas
98781fac6d DataModel: Ticket obsolescence condition: fix issue with 'rejected' UserRequest which have no 'close_date'
SVN:trunk[4801]
2017-07-06 10:08:06 +00:00
Vincent Dumas
efeee395d1 DataModel: Bug on LogicalInterface obsolescence condition, status does not exist
SVN:trunk[4800]
2017-07-06 10:06:04 +00:00
Denis Flaven
b17505f86e Larger area to be able to add an extra icon/menu if needed.
SVN:trunk[4799]
2017-07-05 15:17:16 +00:00
Vincent Dumas
f89dd77dbc DataModel: definition of obsolescence condition per class
SVN:trunk[4798]
2017-07-05 14:31:17 +00:00
Vincent Dumas
f8b6fb51c2 Ticket transitions: 'reassign' force change of agent, agent and team in 'assigned' state are read-only, user satisfaction and user comment are read-only in 'closed' state.
In Enhanced Portal: 're-open' prompt for caselog and 'on-going tickets' brick has a modified label (english and french) and ticket count is now displayed on each tab.

SVN:trunk[4797]
2017-07-04 15:50:20 +00:00
Guillaume Lajarige
d80c2293dc N°380 Fixed UI in console, object details that were going over their container sometimes
SVN:trunk[4796]
2017-07-04 11:58:40 +00:00
Guillaume Lajarige
3cecdeff1c N°380 Fixed object header width in console UI.
SVN:trunk[4795]
2017-07-04 08:32:14 +00:00
Romain Quetiez
3f819eaa19 N.656 Allow DB writes during a backup
SVN:trunk[4794]
2017-07-04 06:55:41 +00:00
Guillaume Lajarige
01de060093 N°642 Portal: Flags on transition, fixed a bug with state form not reading DM flags correctly.
SVN:trunk[4793]
2017-07-03 15:37:11 +00:00
Denis Flaven
7c39a8baf1 [CKEditor] Enabled the "Source mode edition" button, by popular demand. Be aware that the resulting HTML source is anyhow filtered/validated/sanitized by CKEditor itself, then iTop.
SVN:trunk[4792]
2017-07-03 15:17:39 +00:00
Denis Flaven
ebcaaa089a Updated CKEditor to the latest version (4.7.1)
SVN:trunk[4791]
2017-07-03 15:11:06 +00:00
Guillaume Lajarige
121635d636 N°642 Portal: Flags on transition part 3
SVN:trunk[4790]
2017-07-03 14:24:59 +00:00
Guillaume Lajarige
68885496dd N°642 Portal: Flags on transition part 3, forms inheritance for stimuli.
SVN:trunk[4789]
2017-07-03 10:25:49 +00:00
Guillaume Lajarige
9a56c3acfd N°642 Portal: Flags on transition part 2. Fixed a bug on transition with field that should have been must_xxx from DM
SVN:trunk[4788]
2017-07-03 07:33:46 +00:00
Guillaume Lajarige
a0259636b1 N°642 Portal: Flags on transition part 2
SVN:trunk[4787]
2017-07-03 06:57:36 +00:00
Romain Quetiez
0844beca79 Obsolescence: do not lose external keys pointing to obsolete data
SVN:trunk[4786]
2017-06-30 14:06:08 +00:00
Romain Quetiez
dbe3e94d5c Obsolescence: audit errors reporting to include obsolete data as well as the audit execution does
SVN:trunk[4785]
2017-06-30 13:25:02 +00:00
Vincent Dumas
9a43083b3b Fix typos in German dictionary for UI
SVN:trunk[4784]
2017-06-30 13:20:02 +00:00
Romain Quetiez
c6e472b98b Archive mode: user preference "show obsolete data" is automatically checked, and disabled
SVN:trunk[4783]
2017-06-30 12:59:28 +00:00
Romain Quetiez
0834e36b38 Archive: fix dashboard management (edition, revert, from file) when archive mode is on
SVN:trunk[4782]
2017-06-30 12:51:09 +00:00
Romain Quetiez
f7cf825975 Archive: fix shortcut creation/renaming/deletion when archive mode is on
SVN:trunk[4781]
2017-06-30 12:21:28 +00:00
Romain Quetiez
189c03dfea Obsolescence: count mismatch for the global search feature
SVN:trunk[4780]
2017-06-30 12:14:31 +00:00
Romain Quetiez
990be7a105 Archive mode: fixed the recording of user preferences
SVN:trunk[4779]
2017-06-30 10:00:38 +00:00
Romain Quetiez
813c80499c Archive: fixed regression on the exports (N.890.8)
SVN:trunk[4778]
2017-06-30 08:21:43 +00:00
Guillaume Lajarige
495cedc04f Fixed UI in console edit forms that were going over their container sometimes
SVN:trunk[4777]
2017-06-29 15:49:57 +00:00
Romain Quetiez
cbf2919dcd Obsolescence: fixed the case when an external field points to an external key that is obsoletable (or archivable), causing an error during the setup (view could not be created)
SVN:trunk[4776]
2017-06-29 15:17:17 +00:00
Guillaume Lajarige
7cbdfaa5d4 Restoring /log/.htaccess filename which was renamed by mistake.
SVN:trunk[4775]
2017-06-29 08:29:33 +00:00
Guillaume Lajarige
374ce20d0c N°642 Portal: Flags on transition
SVN:trunk[4774]
2017-06-29 08:03:35 +00:00
Romain Quetiez
1dbc2051cc Obsolescence: fixed regression introduces in [r4766], count of 1-N link sets not matching the result set
SVN:trunk[4773]
2017-06-28 08:52:30 +00:00
Romain Quetiez
7e38d4be50 Fixed Regression introduced with the rework of N-N links (bug when creating software instances on a server)
SVN:trunk[4772]
2017-06-27 14:18:28 +00:00
Romain Quetiez
8451ffdfb4 Regression introduced with the implementation of obsolescence: first install not working (Access denied for user ''@localhost)
SVN:trunk[4771]
2017-06-27 13:58:38 +00:00
Romain Quetiez
c2b9716e28 Obsolescence: fixed OQL issue
SVN:trunk[4770]
2017-06-27 12:37:07 +00:00
Guillaume Lajarige
b15621639b N°653: Dependant fields on transitions was not using transition flags.
SVN:trunk[4769]
2017-06-27 10:14:58 +00:00
Guillaume Lajarige
4ca998ce91 N°642 Portal: Transitions configuration part 1.
SVN:trunk[4768]
2017-06-26 16:00:07 +00:00
Guillaume Lajarige
71ec3da4ac N°653 Portal: Lifecycle: Flags on transitions part 2
SVN:trunk[4767]
2017-06-22 13:38:45 +00:00
Romain Quetiez
43b8522b85 Rework of the edition of 1-N and N-N links: managed as a delta from the GUI down to the the lowest APIs.
- Fixes the management of obsolete linked data.
- N.744 Fixes concurrent modifications (example: a user modifies a team, another user modifies a person related to that same team). Still NOT fixed with the customer portal.
- N.849 Fixes links edition in the case some data are not allowed to the current user (organization silos) -TO BE TESTED
- #1145 Fixes the creation of duplicate links in one step (Server to NW Device)
- #1147 Fixes the update of duplicate links

SVN:trunk[4766]
2017-06-21 15:47:28 +00:00
Guillaume Lajarige
8b820ce403 N°900 Portal: Fixed wrong count on ManageBrick tabs
SVN:trunk[4765]
2017-06-20 15:28:31 +00:00
Guillaume Lajarige
4301a5d39d Portal: Fixed page scrolling when clicking on FilterBrick's tile on home page.
SVN:trunk[4764]
2017-06-20 14:22:18 +00:00
Guillaume Lajarige
51519a3659 Portal: Added TWIG hooks for logo overloading in the navigation menu.
SVN:trunk[4763]
2017-06-20 09:54:02 +00:00
Romain Quetiez
2693e81bad XML Customizations: when the parent class is unknown, the error is "unknown constant PARENT_NOT_FOUND"... which is a clue ;-)... now it says something far more accurate like "/itop_design/classes/class[MyCustomClass] at line 458: parent class 'SomeOtherCustomClass' could not be found"
SVN:trunk[4762]
2017-06-14 15:14:49 +00:00
Guillaume Lajarige
eca746f558 N°653 Ticket Lifecycle: Part 2, core & console done.
SVN:trunk[4761]
2017-06-09 07:06:40 +00:00
Denis Flaven
01865ed7f9 More meaningful default values for the version/revision of iTop.
SVN:trunk[4760]
2017-06-08 13:57:05 +00:00
Denis Flaven
14dfe04714 Instrumentation of the setup/compiler: dump the complete XML data model at the end of the installation.
SVN:trunk[4759]
2017-06-08 13:56:14 +00:00
Guillaume Lajarige
befa0b4429 N°900 Portal: Tabs in ManageBrick can now display the objects count.
SVN:trunk[4758]
2017-06-07 14:13:02 +00:00
Denis Flaven
68ff589f9c (refactoring) Generate database and "instance" UUIDs upon installation/upgrade.
SVN:trunk[4757]
2017-06-06 14:00:10 +00:00
Denis Flaven
04e1f32860 (refactoring) Added a method to generate UUIDs.
SVN:trunk[4756]
2017-06-06 13:59:01 +00:00
Romain Quetiez
636356f479 N.897 Object having too many external keys could not be recorded (61 tables)
SVN:trunk[4755]
2017-06-01 12:44:21 +00:00
Guillaume Lajarige
313ea72017 Portal: Added icons to error page buttons
SVN:trunk[4754]
2017-05-31 09:01:02 +00:00
Guillaume Lajarige
98b4a0178f Portal: Added a "Reload page" button on the error page.
SVN:trunk[4753]
2017-05-31 08:55:48 +00:00
Guillaume Lajarige
602be73d0b N°873 Portal: Picture/Preferences/Password forms can now be disabled in the user profile
SVN:trunk[4752]
2017-05-29 16:14:21 +00:00
Guillaume Lajarige
9bfc9a0a76 Portal: New customization hooks in user profile twig
SVN:trunk[4751]
2017-05-29 15:10:35 +00:00
Guillaume Lajarige
7e6a040983 Portal: User profile form was broken due to new display_mode property
SVN:trunk[4750]
2017-05-29 14:50:57 +00:00
Guillaume Lajarige
905d47cab5 Portal: CKEditor fields now have different height regarding the form layout (cosy, compact, dense)
SVN:trunk[4749]
2017-05-23 13:15:22 +00:00
Romain Quetiez
2118a5da71 N.667 Metamodel introspection (schema.php) to display lifecycle labels and codes (though the code were already visible in a tooltip).
SVN:trunk[4748]
2017-05-22 15:35:16 +00:00
Romain Quetiez
c2c0221535 N.542 Refactored the code so that query placeholders (current_contact->attcode or current_user->attcode) become available in template too.... BUT this will not work if the notifications are sent asynchronously (thus the logged in user is an account dedicated to the cron).
SVN:trunk[4747]
2017-05-22 14:57:03 +00:00
Romain Quetiez
ba0a9709f4 Test: fixed automated tests that were abusively failing due to a few new magic attributes
SVN:trunk[4746]
2017-05-22 14:04:50 +00:00
Romain Quetiez
80121b89c3 Obsolescence: background task setting (or resetting) the obsolescence date for obsolete data. The periodicity can be tuned by the mean of obsolescence.date_update_interval, defaulting to 10 minutes. Also renamed show_obsolete_data into obsolescence.show_obsolete_data for consistency.
SVN:trunk[4745]
2017-05-19 09:20:13 +00:00
Romain Quetiez
3b48428897 Internal: DBObjectSearch->AddCondition, allow the case $value = null (otherwise failing because the keyword null has not yet been implemented in OQL)
SVN:trunk[4744]
2017-05-19 09:15:22 +00:00
Guillaume Lajarige
018d6a98e9 Portal: Debug mode now logs external keys OQL queries used in forms. Helpful to understand the final query (DM OQL intersect Scope OQL)
SVN:trunk[4743]
2017-05-19 08:51:59 +00:00
Guillaume Lajarige
522108dc69 N°879 Portal: Notification URLs poiting to a portal were not working when several portal instances were configured.
SVN:trunk[4742]
2017-05-19 08:30:34 +00:00
Romain Quetiez
5451ced894 Obsolescence: hide rows for which any of the selected class is obsolete (SELECT a, b FROM ...)
SVN:trunk[4741]
2017-05-17 13:22:56 +00:00
Romain Quetiez
944c24e18f Internal: document variables to ease code browsing
SVN:trunk[4740]
2017-05-17 13:21:09 +00:00
Romain Quetiez
248f8d6fd4 Obsolescence: finalizing the implementation of the flag (hide obsolete objects, show an icon on hyperlinks and a tag on the object details, user preference defaulting to the new setting 'show_obsolete_data')
SVN:trunk[4739]
2017-05-17 08:51:10 +00:00
Guillaume Lajarige
ebe467b77a N°635 Portal: Final touches on forms layout refactoring
SVN:trunk[4738]
2017-05-16 14:50:51 +00:00
Guillaume Lajarige
e9b7ccd475 Advanced customization: New overridable verb DBObject::GetDefaultValue($sAttCode)
SVN:trunk[4737]
2017-05-16 09:50:24 +00:00
Guillaume Lajarige
b213f2baea N°635 Portal: Forms now have 3 differents layout (display_mode), see online documentation (when released) for more information.
SVN:trunk[4736]
2017-05-15 15:50:12 +00:00
Guillaume Lajarige
50970810d2 N°635 Portal: Forms now have 2 differents layout (display_mode), see online documentation (when released) for more information.
Note: This is a first step, some refactoring could be done soon.

SVN:trunk[4735]
2017-05-15 12:50:09 +00:00
Guillaume Lajarige
17fbc504e2 Internal: Added some PHP Doc in MetaModel class
SVN:trunk[4734]
2017-05-15 12:42:14 +00:00
Guillaume Lajarige
237980097d Portal: Form field with custom css classes in twig definition were not included in the form. (Note: The CSS class is still absent from the rendering but this is because of the field_set.js widget that needs some upgrade in the _prepareField method)
SVN:trunk[4733]
2017-05-12 12:58:05 +00:00
Romain Quetiez
44b53e40a0 Obsolescence: introduction of this new concept (wording could change later). The obsolescence of an object is computed after other attributes, by the mean of an OQL expression. The code has been refactored (again) so as to factorize between the computation of friendly names and the computation of obsolescence flags. The refactoring involved a significant AND RISKY change: external key friendly names (magic attributes) have been changed from AttributeFriendlyName to AttributeExternalField, which simplifies the SQL query build logic.
SVN:trunk[4732]
2017-05-11 11:58:42 +00:00
Guillaume Lajarige
6c6ad0a45b Portal: Fixed a regression introduced in r4724. User profile page could not be opened anymore.
SVN:trunk[4731]
2017-05-11 08:36:22 +00:00
Guillaume Lajarige
623fa8ec63 Portal: Fixed regression on always_show_submit form option
SVN:trunk[4730]
2017-05-10 15:48:57 +00:00
Guillaume Lajarige
02c79fd0a2 Portal: BrowseBrick: First level automatically opens when there is only one item in it, in order to display its subitems.
The opening of the root level is visible by the user so he can understand he is in a sublevel, not wondering where the root level went.

SVN:trunk[4729]
2017-05-10 15:22:13 +00:00
Guillaume Lajarige
05bb797768 Portal: Renamed Grid browse mode to Mosaic
SVN:trunk[4728]
2017-05-10 15:02:23 +00:00
Guillaume Lajarige
096cfdc529 Portal: BrowseBrick: Primary action is now also displayed in the secondary actions menu
SVN:trunk[4727]
2017-05-10 14:44:15 +00:00
Romain Quetiez
5354b0b32b Internal: getting rid of the unused API GetExtKeyFriends, which initialization was not compatible with the declaration of <extkey>_friendlyname as external fields
SVN:trunk[4726]
2017-05-10 14:34:40 +00:00
Guillaume Lajarige
88ec528071 N°635 Portal: Submit button in edit forms is now labelled "Update"
SVN:trunk[4725]
2017-05-10 14:33:44 +00:00
Guillaume Lajarige
5584d42813 N°635 Portal: Parameter to show submit ("Update") button in forms only when there is no transition available.
SVN:trunk[4724]
2017-05-10 14:28:07 +00:00
Vincent Dumas
b696b140b2 Fix typo in french label of lnkContractToDocument class name
SVN:trunk[4723]
2017-05-09 16:34:03 +00:00
Guillaume Lajarige
1de6ac1765 Portal: AttributeImage can now be displayed in forms
SVN:trunk[4722]
2017-05-09 12:27:25 +00:00
Romain Quetiez
d2d895fdf5 Archives: regression. Forms not working with various symptom having the same root cause: DBObject::Set issuing an exception "Attempting to set the value on the read-only attribute operational_status"
SVN:trunk[4721]
2017-05-09 12:07:43 +00:00
Romain Quetiez
a8ad3004ea Internal:
- code refactoring to generalize attributes based on an OQL expression (friendly name, obsolescence flag, ....). The intermediate class AttributeComputedFieldVoid has been swept in favor of the use of a new method: IsBasedOnOQLExpresssion.
- added an introspection API (experimental), allowing an external application to request for information about the capabilities of the framework (first step: list attributes and their main characteristics)

SVN:trunk[4720]
2017-05-05 15:08:49 +00:00
Guillaume Lajarige
b707db9364 Portal: Fixed action rules that were not working on CreateBrick
SVN:trunk[4719]
2017-05-03 14:29:48 +00:00
Guillaume Lajarige
36c53249a0 Portal: Performance optimization on ManageBrick
SVN:trunk[4718]
2017-05-03 12:39:25 +00:00
Guillaume Lajarige
f9ed88a084 Internal: OQL error reporting
SVN:trunk[4717]
2017-05-02 09:11:00 +00:00
Romain Quetiez
294964b227 N.858 (partial) Allow administrators to access the Admin tools menu even in Read-only modes (access_mode or Archive mode). Still, the Data administration menu gets hidden in Read-only modes.
SVN:trunk[4716]
2017-04-28 14:11:00 +00:00
Guillaume Lajarige
8a6fba1981 Portal: SecurityHelper now uses a cache when checking if an object is within a scope or not.
SVN:trunk[4715]
2017-04-28 09:46:05 +00:00
Guillaume Lajarige
1fe0ce5640 Portal: Optimize security helper by removing unnecessary MetaModel::GetObject() call as the check was already done by the scope query. This as significative impact on some pages.
SVN:trunk[4714]
2017-04-28 08:31:30 +00:00
Guillaume Lajarige
7bbe907edc Portal: ManageBrick optimization by loading "always_in_tables" attributes to avoid unnecessary object reloads
SVN:trunk[4713]
2017-04-28 08:20:21 +00:00
Romain Quetiez
6f7d364826 Cleanup: removed unnecessary code
SVN:trunk[4712]
2017-04-27 16:03:23 +00:00
Romain Quetiez
ea2681e08c Archives: added the option with_archive to export web services (defaults to 0)
SVN:trunk[4711]
2017-04-27 15:03:24 +00:00
Romain Quetiez
c66728e478 Cleanup: unused code
SVN:trunk[4710]
2017-04-27 14:02:02 +00:00
Guillaume Lajarige
ba9c6bd8b3 Portal: Twig cache is now disabled when debug option is activated
SVN:trunk[4709]
2017-04-27 13:30:54 +00:00
Romain Quetiez
b23b468c0f Cleanup: unused code
SVN:trunk[4708]
2017-04-27 12:54:18 +00:00
Romain Quetiez
f5144c2bb1 Archives: filter out archived objects from the impact analysis, even when in Archive Mode
SVN:trunk[4707]
2017-04-27 12:42:50 +00:00
Guillaume Lajarige
ee95dd2480 Portal: Worked on some CSS optimizations in forms
SVN:trunk[4706]
2017-04-27 11:40:05 +00:00
Guillaume Lajarige
c1805ce47f Portal: Person form defined in order to restrict the visible attributes
SVN:trunk[4705]
2017-04-27 11:39:05 +00:00
Romain Quetiez
23556e3a43 Archives: clean breadcrumb entry if attempting to navigate to the details of an archived object (while not being in archive mode)
SVN:trunk[4704]
2017-04-27 09:56:05 +00:00
Romain Quetiez
e70e1de75e Archives: archivability must be set on root classes
SVN:trunk[4703]
2017-04-27 09:27:02 +00:00
Romain Quetiez
091b989715 Internal: if an exception is thrown during the initialization of MetaModel, do not attempt to report it into the database (may still be not initialized)
SVN:trunk[4702]
2017-04-27 09:17:39 +00:00
Romain Quetiez
b238283104 Archives: show the menu 'activate archive mode' only if there is at least one archivable class
SVN:trunk[4701]
2017-04-27 08:53:23 +00:00
Romain Quetiez
de2eed5187 Archives: allow for the update of archive_flag even when in archive mode (therefore in read-only mode)
SVN:trunk[4700]
2017-04-27 08:45:07 +00:00
Romain Quetiez
8e046cafda Archives: default label for the magic attributes archive_flag and archive_date
SVN:trunk[4699]
2017-04-27 08:43:45 +00:00
Guillaume Lajarige
f02df401ff Portal: Primary action on a ServiceSubcategory of the services catalog is now the creation of a new UserRequest. Previous one (Viewing details of the ServiceSubcategory) now becomes the secondary action.
SVN:trunk[4698]
2017-04-27 08:23:53 +00:00
Romain Quetiez
28eba2512a Archives: cosmetics on object details
SVN:trunk[4697]
2017-04-26 15:48:57 +00:00
Romain Quetiez
e3931274ae Archives: refactoring of the API to enable/disable the archive mode
SVN:trunk[4696]
2017-04-26 15:48:24 +00:00
Guillaume Lajarige
a55245d203 Portal: Minor CSS fixes on Combodo BS theme.
SVN:trunk[4695]
2017-04-26 14:54:24 +00:00
Guillaume Lajarige
7b0acbdad3 Portal: BrowseBrick: Introducing new browse mode "grid". Also some UI improvements in other browse modes.
SVN:trunk[4694]
2017-04-26 13:20:31 +00:00
Romain Quetiez
9cc583c47b Archives: regression (warning during the setup)
SVN:trunk[4693]
2017-04-26 10:27:25 +00:00
Romain Quetiez
fa2fd6dcdf NEW! Archiving data. Archiving is a soft delete. It can be undone. Enter the archive mode to see all the data including archives (everything is read-only in that mode). Archiving must be enabled per class (data model). Archiving is achieved by the mean of the API DBObject::Archive (or Unarchive).
SVN:trunk[4692]
2017-04-26 09:52:20 +00:00
Guillaume Lajarige
9d242e1623 N°848 Portal: Option to display LinkedSet as opened in a form
SVN:trunk[4691]
2017-04-26 09:10:24 +00:00
Romain Quetiez
ec6a8537e9 Refactoring: font awesome can be used by all the application
SVN:trunk[4690]
2017-04-25 13:48:22 +00:00
Romain Quetiez
402e95b9e2 Reincorporate font-awesome into the core
SVN:trunk[4689]
2017-04-25 13:24:37 +00:00
Guillaume Lajarige
2fcf50bb88 N°653 Lifecycle flags can be defined on both states and transitions (Note: This is a beta version and need to be tested!)
SVN:trunk[4688]
2017-04-21 14:59:04 +00:00
Guillaume Lajarige
2e8c629195 N°829 Portal: AttributeUrl was not clickable in the new portal
SVN:trunk[4686]
2017-04-19 15:36:00 +00:00
Guillaume Lajarige
9c5b3818eb Portal: Refactor a security check to remove unnecessary security messages in error.log
SVN:trunk[4684]
2017-04-18 13:51:25 +00:00
Denis Flaven
c9bb27a3ff Declare the ApplyStimulus method, for use by the Designer.
SVN:trunk[4683]
2017-04-18 13:29:46 +00:00
Vincent Dumas
f96a2092e7 Readme iTop Community 2.3.4
SVN:trunk[4681]
2017-04-14 15:34:07 +00:00
Vincent Dumas
5dafb92dd3 LOCK TABLE MySQL privilege is required for iTop user to restore backups.
SVN:trunk[4679]
2017-04-13 09:46:07 +00:00
Guillaume Lajarige
63f0a0ef11 Internal: Added comment about portal logo priority order
SVN:trunk[4678]
2017-04-12 07:50:45 +00:00
Romain Quetiez
bb9236deae N.816 New parameter in the working time computer GetDeadline API: the 4th parameter is the threshold for which the deadline is being computed.
SVN:trunk[4675]
2017-04-06 19:07:08 +00:00
Romain Quetiez
1a66a6491a N.755 Case log preset by object copier is not correcly displayed in the object creation form (if combined with the copy of the previous log AND if that log has at least one entry). Has never worked.
SVN:trunk[4672]
2017-04-06 18:44:34 +00:00
Guillaume Lajarige
1528d85f5f N°637 Portal: Better feedback on exceptions and user session timeout on AJAX calls
SVN:trunk[4671]
2017-04-06 14:21:27 +00:00
Romain Quetiez
3773a88cc1 N.417 Object name displayed with html entities (e.g. "&" shown as "&amp;") when selecting/creating an object into an autocomplete
SVN:trunk[4670]
2017-04-05 14:39:13 +00:00
Romain Quetiez
856a73fb87 N.689 61 tables limit. INVESTIGATING the issue. Added a test to reproduce the issue on the standard data model, and a test to build statistics on the number of tables used by classes.
SVN:trunk[4669]
2017-04-05 12:58:33 +00:00
Romain Quetiez
d0bc9a0bb8 Automatic test suite: fixed warnings (benchmarked queries processed instantaneously);
SVN:trunk[4668]
2017-04-05 10:26:43 +00:00
Romain Quetiez
751b7c8560 N.755 Case log latest entry preset with a copy of a case log coming from another ticket. The log gets corrupted after adding a new entry.
SVN:trunk[4666]
2017-04-04 12:58:02 +00:00
Guillaume Lajarige
4fccae157b Portal: CaseLog fields value was kept in the form after object update instead of being reset.
SVN:trunk[4664]
2017-04-04 10:17:17 +00:00
Romain Quetiez
85dc7c9452 N.755 Case log latest entry preset with HTML code gets transformed into pure text
SVN:trunk[4662]
2017-04-04 09:25:02 +00:00
Guillaume Lajarige
1cf25e49ae N°807 Portal: add_to_list can now be used in action rules (Note: Works only for IndirectLinkedSet, not LinkedSet)
SVN:trunk[4660]
2017-04-04 09:07:06 +00:00
Guillaume Lajarige
ab6cd49fcb Portal: Fixed friendlyname fields rendered as mandatory when they should have been readonly.
SVN:trunk[4658]
2017-04-04 08:01:47 +00:00
Romain Quetiez
5411e60c81 N.755 (reduces the impact) Core API: Fixed the merging of several entries into a case log (calling several times DBObject::Set for a case log). The previous implementation did not work at all, leaving the data structure inconsistent. Moreover, the last text given is now inserted AFTER the text it is merged with (not on top of it) like in a normal text stream.
SVN:trunk[4657]
2017-04-03 20:56:47 +00:00
Denis Flaven
15da430459 N°686 - protect the edition of dashboards against a no-longer-existing class.
SVN:trunk[4655]
2017-04-03 14:49:49 +00:00
Romain Quetiez
5adb0fd556 N.447 Impact analysis: messing up redundancy settings (could either lead to wrong results or a fatal error if a relation is configured downstream)
SVN:trunk[4653]
2017-04-03 13:59:06 +00:00
Guillaume Lajarige
2876d7e08c N°806 Portal: Wrong form used in some inheritance cases.
SVN:trunk[4651]
2017-04-03 11:57:29 +00:00
Romain Quetiez
59c3f565a3 Fixes a regression introduced in [r4642]. N.450 Impact analysis. The previous fix aimed at generating more redundancy nodes, but in fact it generated too many in some circumstances.
SVN:trunk[4649]
2017-04-01 20:05:14 +00:00
Guillaume Lajarige
86191b36d8 DBObject::ExecActions : add_to_list action now accepts the source object id as first parameter
SVN:trunk[4647]
2017-03-31 15:18:50 +00:00
Vincent Dumas
b737252076 n°519 add examples to REST/JSON on updating CaseLog with Text format
SVN:trunk[4646]
2017-03-31 15:17:23 +00:00
Romain Quetiez
e688d04511 N.788 Impact analysis: graph not refreshed when trying to filter out some classes. The node removal algorithm has been improved in two places. 1) Do not create loops (i.e. an edge having both ends on the same node) when removing a node. 2) Correctly cleanup nodes having a loop (in case there is a loop in the original graph (defensive programming)
SVN:trunk[4644]
2017-03-31 14:48:30 +00:00
Romain Quetiez
76fd5c0b5f N.450 Impact analysis : missing edges (and redundancy) when two classes impact a given class and both relations use the same neighbour id (and if redundancy is enabled over both relations)
SVN:trunk[4642]
2017-03-31 14:34:43 +00:00
Guillaume Lajarige
d944f386ce N°800 Portal: log_kpi_duration / log_kpi_memory are now supported by the portal
SVN:trunk[4640]
2017-03-31 12:59:35 +00:00
Guillaume Lajarige
9f9fd8550f N°804 Portal: Object display crashed when a linkedset attribute has corrupted data (eg. an external key to 0)
SVN:trunk[4638]
2017-03-31 10:09:51 +00:00
Romain Quetiez
772954711a N.799 Setup failing (during database creation) with MetaEnum attribute having no mapping for the class they are declared in.
SVN:trunk[4636]
2017-03-30 19:23:43 +00:00
Romain Quetiez
3644553886 N.609 Some multi-column UNION queries failing with various symptoms such as "Class 'IT Department' not found" or "An object id must be an integer value"
SVN:trunk[4634]
2017-03-29 19:58:02 +00:00
Romain Quetiez
c073611597 N.710 Do not notify ignored when the impacted items is computed again. Side effect: PHP Notice: Undefined offset: N in .../itop-tickets/main.itop-tickets.php on line 263.
SVN:trunk[4632]
2017-03-29 10:02:14 +00:00
Romain Quetiez
802d20d554 N.780 Friendly name format ignored if only one attribute is used
SVN:trunk[4630]
2017-03-29 09:22:39 +00:00
Romain Quetiez
38c12104b3 N.740 Error when attempting to disable the logs. Took the opportunity to refactor, relying on late static bindings (requires PHP 5.3). The refactoring avoided the fix to be duplicated in three places.
SVN:trunk[4628]
2017-03-29 08:28:24 +00:00
Romain Quetiez
399662ef99 N.701 (continuation of [r4596] which introduced regressions on the handling of date fields)
SVN:trunk[4626]
2017-03-28 14:56:52 +00:00
Denis Flaven
3daf4c62db - Refactoring : structuration of the Exceptions thrown when the XML assembling fails
- Take into account the node specified as a parameter to saveXML()

SVN:trunk[4624]
2017-03-27 16:24:50 +00:00
Guillaume Lajarige
9937f62f9d N°641 Portal: Browse and Manage bricks now have an optional <opening_target> tag to choose how to open items (modal, new tab, current window).
SVN:trunk[4623]
2017-03-24 09:09:15 +00:00
Romain Quetiez
cf17e197ce N.760 XSS vulnerability
SVN:trunk[4621]
2017-03-23 16:32:56 +00:00
Romain Quetiez
2f8b5e5eeb N.519 REST/JSON Add a comment in pure text into the case log (only for add_item/items syntaxes, use format="text")
SVN:trunk[4619]
2017-03-23 15:16:30 +00:00
Romain Quetiez
d22fb83443 N.736 Ugly labels when hovering bar charts
SVN:trunk[4617]
2017-03-23 14:52:57 +00:00
Romain Quetiez
af093ba86a N.692 Config setting log_queries does not work? This setting has been deprecated (use log_kpi_duration instead) - Still the implementation of query logging remains (and is buggy) as it may be usefull in order to record and rebuild a complete set of queries.
SVN:trunk[4615]
2017-03-23 09:09:45 +00:00
Romain Quetiez
020f47ab2c Code cleanup: removed unused code
SVN:trunk[4614]
2017-03-22 21:24:50 +00:00
Romain Quetiez
8f0095f751 N.718 (continuation) Audit failing with message "Attempting to merge a filter of class A with a filter of class B" (regression introduced in branch 2.3)....
SVN:trunk[4612]
2017-03-22 20:59:33 +00:00
Romain Quetiez
b7fc2e4012 N.718 Audit failing with message "Attempting to merge a filter of class A with a filter of class B" (regression introduced in branch 2.3). There are circumstances for which the queries cannot (yet) be optimized (fallback to the original algorithm)
SVN:trunk[4611]
2017-03-22 20:53:40 +00:00
Guillaume Lajarige
1dcf830141 N°783 Attachments can now be readonly in some states. Use the new module parameter "readonly_states".
SVN:trunk[4610]
2017-03-22 15:08:34 +00:00
Romain Quetiez
a10cdaba74 N.779 PARTIAL FIX - Date fields in link sets cannot be edited if the date format differs from the native format (Y-m-d). Homogeneous way to call the API GetFormElementForField, with GetEditValue as the "display value". Still, submitting the form does not work.
SVN:trunk[4609]
2017-03-22 13:39:48 +00:00
Romain Quetiez
435a177283 Suppressed warnings due to the error reporting. When one of the arguments in the call stack is an array, a warning (Conversion of an array into a string) is issued at the time the EventIssue is being recorded. When a posted parameter is an array, the same warning is issued at the time and EventIssue is being displayed.
SVN:trunk[4608]
2017-03-22 12:43:12 +00:00
Vincent Dumas
d22d657886 #1439 fix erroneous internal comments within XML files
SVN:trunk[4607]
2017-03-21 17:35:38 +00:00
Guillaume Lajarige
1da5618204 N°606 Portal: Request template fields marked as invalid when a mandatory textarea field was empty.
SVN:trunk[4605]
2017-03-21 09:53:57 +00:00
Vincent Dumas
318c069f3d Corrigé une typo dans un dictionnaire français
SVN:trunk[4604]
2017-03-17 13:32:30 +00:00
Denis Flaven
d0267f60ae Make sure that the generated form field's IDs are valid ones.
SVN:trunk[4602]
2017-03-17 13:20:01 +00:00
Denis Flaven
ea3c7703c4 XML format 1.4, introducing the new "force" flag.
SVN:trunk[4601]
2017-03-17 13:15:13 +00:00
Guillaume Lajarige
95e04178ea N°636 Portal: Action buttons can now be added to object details page through the iPopupMenuItemExtension
SVN:trunk[4600]
2017-03-17 13:03:09 +00:00
Denis Flaven
cf0792cd64 More events from the property_field widgets to inform the enclosing form of the state changes.
SVN:trunk[4598]
2017-03-17 10:55:06 +00:00
Romain Quetiez
e41a8833e2 N.701 Data Synchro: dates can be reset by the mean of an empty string (still, integers and enums cannot be reset)
SVN:trunk[4596]
2017-03-17 09:14:11 +00:00
Romain Quetiez
0b2ce4289d 738 Setup not working if access_mode=2 and a synchro data source has a new attribute to create
SVN:trunk[4594]
2017-03-16 15:22:19 +00:00
Guillaume Lajarige
a13270ef91 N°772 Portal: Invalid URL in LinkedSet searchbox when editing an object (eg. Adding a Contact to an UserRequest)
SVN:trunk[4592]
2017-03-16 13:14:45 +00:00
Guillaume Lajarige
64ca121f5b Portal: Used BS breadcrumb in BrowseBrick instead of the previous which was home-made.
SVN:trunk[4591]
2017-03-15 17:03:32 +00:00
Romain Quetiez
0a5708272d N.678 Data synchro: a line break or '<' in the description breaks the synchronized objects edition form.
SVN:trunk[4589]
2017-03-15 14:41:12 +00:00
Guillaume Lajarige
fec15ffe66 Portal: FilterBrick: Value for placeholder and submit button can now be empty
SVN:trunk[4588]
2017-03-14 16:09:13 +00:00
Guillaume Lajarige
0b0340bf21 Internal: CSS optimization on portal BrowseBrick
SVN:trunk[4587]
2017-03-14 15:42:29 +00:00
Romain Quetiez
cf716ce2a3 N.598 Custom fields with autocomplete failing if the subfield depends on another subfield
SVN:trunk[4585]
2017-03-14 15:20:50 +00:00
Guillaume Lajarige
5ebcb41224 Portal: Added breadcrumbs to grid mode of the BrowseBrick
SVN:trunk[4584]
2017-03-14 13:59:05 +00:00
Romain Quetiez
2d9c0e16b9 N.569 Enable the browser built-in spell checker for the rich text editor
SVN:trunk[4582]
2017-03-14 13:42:20 +00:00
Romain Quetiez
20e0ab3d6e N.453 Emails coming from outlook. Many line breaks added when editing the ticket
SVN:trunk[4580]
2017-03-14 13:16:42 +00:00
Vincent Dumas
d94ccc441a N°675 enriched Dutch Dictionnaries for Enhanced portal
SVN:trunk[4579]
2017-03-14 09:49:58 +00:00
Vincent Dumas
fdff9e048b N°759 French Dictionnary typo on closed ticket value
SVN:trunk[4578]
2017-03-13 17:29:31 +00:00
Vincent Dumas
64e1d7e276 Problem management: german translations on values for field 'impact' were upside down compared to english values. Value 1 means high impact in all langage but German where is was 'Eine Person'. German has been aligned. German users, be cautious when upgrading to migrate your data.
SVN:trunk[4577]
2017-03-13 17:19:32 +00:00
Vincent Dumas
5f82c78dc7 #1314 (N°585,586,591) allow to use finalclass (sub-class name) as reconciliation key when loading by CSV import, relationship defined on an abstract class.
#1387 (N°577) fixing old issue with CSV import of relationship with Documents

SVN:trunk[4576]
2017-03-13 16:56:03 +00:00
Guillaume Lajarige
b3a0c6119e N°762: Portal: New filter brick that pre-filters a Browse or Manage brick results from the home page.
SVN:trunk[4575]
2017-03-13 14:06:43 +00:00
Romain Quetiez
342ac0444c N.757 Server log filled with warnings (ContextTag::Check)
SVN:trunk[4573]
2017-03-10 14:32:32 +00:00
Vincent Dumas
d9469360fe N°582 Typo on french user message used when changing user password
SVN:trunk[4572]
2017-03-09 10:41:37 +00:00
Vincent Dumas
0df420cd17 N°723 help message enhancement on 4th setup screen, about MySQL user required rights.
SVN:trunk[4571]
2017-03-08 14:16:29 +00:00
Guillaume Lajarige
ab44522016 N°636: Portal: Dictionnary entries can now be used in forms' twig (usage: {{'EntryToTranslate'|dict_s}}). Also, special characters (like 'é') are now supported.
SVN:trunk[4570]
2017-03-08 09:58:12 +00:00
Guillaume Lajarige
bc875653d2 Internal: Portal: Grid display optimization on BrowseBrick
SVN:trunk[4569]
2017-03-07 16:23:48 +00:00
Guillaume Lajarige
ee41204d6c N°565: Portal user and Portal power user scopes alignment in "Closed tickets" brick.
SVN:trunk[4568]
2017-03-07 14:53:57 +00:00
Guillaume Lajarige
c4d1113bb8 N°634: BrowseBrick Windows 8-style display (tiles). This is still a beta version.
SVN:trunk[4567]
2017-03-07 14:25:09 +00:00
Guillaume Lajarige
6582569150 Portal: Fixed home page <body> CSS class
SVN:trunk[4566]
2017-03-03 14:33:42 +00:00
Guillaume Lajarige
69d0ceaf5b Portal: Added CSS classes to <body> tag regarding the current page / brick so we can apply CSS style more precisely.
SVN:trunk[4565]
2017-03-03 13:09:08 +00:00
Denis Flaven
60165d5216 Bug fix: load modules before generating the WSDL file, since some modules may come with their own webservices.
SVN:trunk[4563]
2017-02-28 14:10:11 +00:00
Guillaume Lajarige
76ca7dc9e8 N°602: InlineImage "randomly" not available for display.
Adding an InlineImage while adding an object in a IndirectLinkedSet at the same time would attach the InlineImage to the linked object instead of the host one. If their organizations were different, it could result in a security issue, denying the display of the InlineImage.

SVN:trunk[4561]
2017-02-28 09:47:24 +00:00
Denis Flaven
fcc5342775 Bug fix: protect against a non existing Contact class (a rather drastic iTop customization!)
SVN:trunk[4559]
2017-02-24 14:09:44 +00:00
Guillaume Lajarige
b4534f455a Internal: Fixed the usage of a private function (from Symfony ExceptionHandler) in a derivated class (iTop ExceptionHandler).
SVN:trunk[4558]
2017-02-23 10:49:16 +00:00
Guillaume Lajarige
0d3203476c N°589 - Portal: Displaying a better error message when the portal crashes because the current contact's organization is not among the current user's allowed organizations. More over we introduced an exception handler to display a nicer web page and log the exception in the error log.
SVN:trunk[4557]
2017-02-23 10:07:12 +00:00
Guillaume Lajarige
9fa6157c37 N°587 - Portal: Fixed issue when using multiple action rules in a BrowseBrick and only one was actually used.
SVN:trunk[4556]
2017-02-21 14:26:07 +00:00
Guillaume Lajarige
2ee674f11b N°620 - Fixed regression introduced in r4519: Portal: Url in notifications were broken since iTop 2.3.3.
SVN:trunk[4554]
2017-02-21 09:06:03 +00:00
Guillaume Lajarige
820d7108e4 N°633 - Portal: CreateBrick now accepts abstract classes. This is a refactoring from an extension (CreateBrickExtended) that has been merge into the core.
SVN:trunk[4553]
2017-02-21 08:10:47 +00:00
Guillaume Lajarige
9a6a562ed2 N°619 - Portal: Other allowed portals (eg. the administration console or other portal instances) can now be opened in the current window or a new tab. This is a new xml property of the portal instance.
SVN:trunk[4552]
2017-02-21 08:04:46 +00:00
Guillaume Lajarige
f3f9e98aa4 Portal: Added some hooks in the main twig template
SVN:trunk[4551]
2017-02-10 10:06:38 +00:00
Denis Flaven
fc375da128 N° 615: spreadsheet export enhancement to remove unneeded line breaks.
SVN:trunk[4549]
2017-01-31 13:26:12 +00:00
Denis Flaven
172ceba464 N° 615: fixing the spreadsheet export (for integration into Excel) for HTML formatted text fields (e.g. ticket's description).
SVN:trunk[4547]
2017-01-18 15:17:07 +00:00
Guillaume Lajarige
ecae7ea983 Portal: Browse brick: List was displaying pagination when not necessary in some circonstancies.
SVN:trunk[4546]
2017-01-16 13:08:45 +00:00
Guillaume Lajarige
a204a786c0 Portal: Browse brick: Filtering in list view now looks up in all the displayed fields (<name_att /> and <fields /> of the brick configuration)
SVN:trunk[4545]
2017-01-11 16:32:37 +00:00
Denis Flaven
79a2ab8abd Getting ready for the release of iTop 2.3.3.
SVN:trunk[4542]
2016-12-22 15:14:04 +00:00
Guillaume Lajarige
af13b42eab Legacy portal: Since iTop 2.3, plain text caselog entries can no longer be toggled due to a bad jQuery selector. Only HTML entries were working.
SVN:trunk[4541]
2016-12-21 13:10:15 +00:00
Denis Flaven
75721091f2 Updated Russian dictionary, thanks to Vladimir Kunin.
Note: you can always get the latest version on Vladimir's github: https://github.com/itop-itsm-ru/itop-rus.

SVN:trunk[4540]
2016-12-19 16:34:23 +00:00
Romain Quetiez
6e327e245b N.497 Continuation of the fix done in [r4461], to correctly handle validation patterns containing a slash (AttributeURL in the enhanced customer portal). The initial fix has broken the validation of date (+time) fields because the slash was escaped twice, leading to an invalid regular expression. Requires testing of synchro, CSV import, console, customer portal...
SVN:trunk[4538]
2016-12-19 16:04:21 +00:00
Guillaume Lajarige
6bfead405d N.551: Correcting spanish translations in the new portal (When creating a new user request in full ITIL or changing password)
SVN:trunk[4536]
2016-12-19 10:15:46 +00:00
Denis Flaven
a4f5620076 N. 549: "Font" menu broken in case logs (console and legacy portal). The menu was showing <> instead of font names.
SVN:trunk[4534]
2016-12-19 10:09:48 +00:00
Guillaume Lajarige
134a427901 Portal: Added german dictionnary
SVN:trunk[4532]
2016-12-16 09:13:43 +00:00
Guillaume Lajarige
27c3facb2b Portal: Added an environment banner in the portal like in the console. It warns the user when he is browsing in an other mode than "production".
SVN:trunk[4531]
2016-12-15 15:02:26 +00:00
Denis Flaven
fa9848d1be CKEditor version 4.6.1 + color dialog (for choosing the color of a table cell)
SVN:trunk[4530]
2016-12-14 16:45:22 +00:00
Denis Flaven
1afd7d2ae4 Exclude non-needed folders from the build.
SVN:trunk[4529]
2016-12-14 15:53:20 +00:00
Guillaume Lajarige
c622efe95c Portal: Added itopversion to js/css file urls to prevent cache issues when upgrading.
SVN:trunk[4526]
2016-12-14 11:20:49 +00:00
Denis Flaven
c0949421ad Support of the "target" attribute for links.
SVN:trunk[4524]
2016-12-14 11:16:12 +00:00
Romain Quetiez
0e21edcc77 N.561 Could not add images into the description of a ticket in the legacy portal, and losing images added into the case log (update from the portal, issue occuring when the CRON is enabled)
SVN:trunk[4521]
2016-12-13 16:55:16 +00:00
Denis Flaven
df85186407 N. 481:
1) wiki text syntax was not displayed in the description or case logs of the tickets
2) when wiki text syntax was supported, the generated hyperlinks were pointing to the console (instead of the portal)

SVN:trunk[4519]
2016-12-13 16:16:13 +00:00
Romain Quetiez
403ecf7fba N.557 Date/date+time pickers in the legacy portal, not aligned with the configured date formats. Took the opportunity to implement time picking (thus aligned with any form of iTop)
SVN:trunk[4517]
2016-12-13 14:24:33 +00:00
Denis Flaven
d143f0880b N. 533: when reloading the ticket form (due to an alternate initial state path)
1) the value of some controls (non-text inputs in n:n links) was not preserved,
2) popup dialogs and CKEditor instances were not properly destroyed and re-created.

SVN:trunk[4515]
2016-12-13 10:54:45 +00:00
Guillaume Lajarige
c185e1fc4f Portal: LinkedSet label in forms can now be toggled by clicking on the field label (Was only working on the linkedset count and chevron)
SVN:trunk[4514]
2016-12-13 10:45:34 +00:00
Guillaume Lajarige
68c180f7eb N.474: Support for "file" attribute (AttributeBlob) in the portal. READONLY only for now.
SVN:trunk[4512]
2016-12-13 10:15:43 +00:00
Denis Flaven
c903fc2246 Support of text-align in the styles.
SVN:trunk[4511]
2016-12-08 13:58:01 +00:00
Denis Flaven
7d5898f302 Upgrade of CKEditor from 4.5.8 to 4.6.0 and addition of the formatting buttons:
- Font family
- Font Size
+ reordering of the toolbar buttons to have two lines of equivalent width.

SVN:trunk[4510]
2016-12-08 13:52:28 +00:00
Guillaume Lajarige
a4bdd3aaf4 N.551: Added spanish translations to the enhanced portal thanks to the community :)
SVN:trunk[4508]
2016-12-08 13:21:13 +00:00
Romain Quetiez
5bae9deecc N.545 HTML images not displayed when no login is required for the page.
SVN:trunk[4506]
2016-12-08 12:45:56 +00:00
Guillaume Lajarige
ffbd666aca N.481: Portal: Impossible to submit a form with a duration attribute. Also fixed the displayed value in tables (ManageBrick and BrowseBrick)
SVN:trunk[4504]
2016-12-08 11:36:54 +00:00
Romain Quetiez
4ec3aeec92 N.490 Losing carrier returns and rich text formatting when the latest comments are copied to child tickets
SVN:trunk[4501]
2016-12-08 10:24:06 +00:00
Guillaume Lajarige
c5a00a1bf1 N.546: Portal: Edit value in case log was kept after UserRequest update.
SVN:trunk[4500]
2016-12-08 10:18:59 +00:00
Romain Quetiez
1b1e91f0dc N.500 Changed the query to fetch tickets related to a CI, so as to make it unambiguous whatever customization is made to the datamodel.
SVN:trunk[4498]
2016-12-08 08:22:36 +00:00
Romain Quetiez
21fc272674 N.534 Cannot create a parent ticket from the ticket edition form. More generally, the object creation dialog box (opened by the mean of the PLUS button) fails as soon as any of the mandatory fields is an HTML field. Regression introduced in iTop 2.3.0, and due to HTML field edition widget (aka CKEditor)
SVN:trunk[4496]
2016-12-07 20:31:54 +00:00
Denis Flaven
5716c11450 N. 550 the OpCode cache may cause the upgrade of the datamodel to fail. Let's flush it after the compilation.
SVN:trunk[4494]
2016-12-07 13:13:36 +00:00
Romain Quetiez
f7e77e9fa6 N.539 Regression introduced in [r4451] on oct 7th. Some OQL were issuing a notice and some were generating a SQL query that would fail with error "Column 'functionalci_id' in where clause is ambiguous" (See CI details)
SVN:trunk[4492]
2016-12-05 12:52:08 +00:00
Romain Quetiez
ad8c3db6b4 Continuing [r4488] N.536 Regression introduced in [r4469] (N.505), itself fixing a regression introduced in [r4404]. REQUIRES TESTING
SVN:trunk[4490]
2016-12-05 09:59:19 +00:00
Romain Quetiez
d5c3b8d8e2 N.536 Regression introduced in [r4469] (N.505), itself fixing a regression introduced in [r4404]. REQUIRES TESTING
SVN:trunk[4488]
2016-12-02 20:37:13 +00:00
Romain Quetiez
6ac6aea29f N.480 Page broken (missing menu + ...) when bulk modifying Document Notes (having various values)
SVN:trunk[4486]
2016-12-01 14:06:02 +00:00
Romain Quetiez
7ce06c0797 Optimizations. Continuation of [r4423] and [r4471]. The optimization of the load of icons does not work depending on the itop installation directory and version of PHP (crc32 producing a negative value, not suitable for a class name).
SVN:trunk[4485]
2016-12-01 10:08:35 +00:00
Romain Quetiez
53bf1f424a N.502 Too many backups on sundays
SVN:trunk[4483]
2016-12-01 09:51:47 +00:00
Romain Quetiez
b793cded34 N.527 Enable the template placeholders for AttributeCustomFields
SVN:trunk[4481]
2016-11-25 16:37:31 +00:00
Romain Quetiez
47ec6d4917 N.523 UserRights::ListProfiles must return an empty array if nobody is currently logged in (instead of a FATAL ERROR).
SVN:trunk[4478]
2016-11-18 15:47:20 +00:00
Romain Quetiez
e586ba8d6e N.520 Setup: conflicts when a module in "extensions" is an upgrade of a module that already exists in datamodels/2.x. The most recent module must be installed and the older one must be ignored.
SVN:trunk[4477]
2016-11-17 15:45:34 +00:00
Romain Quetiez
8a913a18cf Cosmetics. Simplified the warning about editing the configuration file. No need to mention which sections can be edited, since ALL other sections have been moved out from the configuration file (includes and dictionnaries).
SVN:trunk[4475]
2016-11-08 16:36:53 +00:00
Denis Flaven
a09e579451 Support of non-case sensitive "forbidden values" in DesignerTextField
SVN:trunk[4473]
2016-11-04 15:58:59 +00:00
Denis Flaven
79c5dc2ce2 Support of "fileref" tags inside the definition of the class fields. Useful for the "default_image" tag of AttributeImage.
SVN:trunk[4472]
2016-11-04 13:34:17 +00:00
Romain Quetiez
95d7bc5319 Continuation of [r4423] Optimizing the scan of icons on disk. Added a LOCK_EX flag to prevent concurrent access related issues. Safe handing of collision on the cache key (used as a filename).
SVN:trunk[4471]
2016-10-28 14:03:17 +00:00
Romain Quetiez
d3f5d05063 N.505 Regression introduced in [r4404]. Security issue - Object visibility totally screwed the APC cache (user data) is enabled. This is a change in the way SQL queries are built and therefore requires testing.
SVN:trunk[4469]
2016-10-28 09:08:30 +00:00
Romain Quetiez
b30a35ceb5 N.504 Could not jump into the designer (APC, random)
SVN:trunk[4467]
2016-10-27 14:57:01 +00:00
Denis Flaven
fac22c9729 Bug fix: creating a new DOM Node containing the string "0" resulted in an empty node (no DOMText).
SVN:trunk[4466]
2016-10-27 08:36:26 +00:00
Romain Quetiez
44e329c38d Cleanup of unused and possibly confusing XML markup (parent node under class/presentation/details). Thanks to D. Gumble for reporting this.
SVN:trunk[4463]
2016-10-21 14:14:20 +00:00
Guillaume Lajarige
e48ad8cb61 #497 Portal : Could not update object due to "Warning: preg_match(): Unknown modifier '/'"
SVN:trunk[4461]
2016-10-21 08:29:36 +00:00
Guillaume Lajarige
359dc73526 #497 Portal : Could not update object due to "Warning: preg_match(): Unknown modifier '/'"
SVN:trunk[4460]
2016-10-20 15:45:17 +00:00
Romain Quetiez
8169e81e9a Automated tests: added tests following the latest optimizations of the query engine
SVN:trunk[4459]
2016-10-14 14:08:52 +00:00
Guillaume Lajarige
f62f087bcc #475 Portal : Could not upload attachments on IE9.
SVN:trunk[4457]
2016-10-14 09:56:45 +00:00
Romain Quetiez
4eb0b3086d N.466 HTML links with href="ftp://..." or "file://...". The filtering implemented by default (DOM Sanitization) now takes the configuration parameter url_validation_pattern into account. Thus aligning the behavior between HTML attributes and AttributeURL, and the automatic wiki formatting. By default, iTop allows the protocols http/https/ftp. To allow the 'file' protocol, edit the config file and change url_validation_pattern accordingly.
SVN:trunk[4455]
2016-10-10 16:01:46 +00:00
Guillaume Lajarige
1c81650572 Graph :
- Bar chart labels on x axis are now displayed vertically like in iTop 2.2. Also, when there are more than 24 labels, not all of them are displayed in order to keep the axis readable.
- Pie chart legend is now placed on the right side.

SVN:trunk[4453]
2016-10-10 13:14:13 +00:00
Romain Quetiez
59e3367da8 N.434 ... Continuation... Fixed regression introduced in [r4448]. OQL (parsed) queries are optimized too but the SQL query cannot be executed. See example herebelow:
SELECT
        UserRequest AS u
   JOIN Person AS p1 ON u.caller_id=p1.id
   JOIN Organization AS o1 ON p1.org_id=o1.id
   JOIN Person ON u.caller_id=Person.id
   JOIN Location AS l ON Person.location_id = l.id
WHERE Person.status='active' AND p1.status='inactive' AND l.country='France'


SVN:trunk[4451]
2016-10-07 13:15:30 +00:00
Guillaume Lajarige
2b4b0fed83 Portal : Final touch to AllowedOrganizations by settings ignore_silos to true for ServiceFamily/Service/ServiceSubcategory on the default portal configuration
SVN:trunk[4449]
2016-10-07 09:39:34 +00:00
Romain Quetiez
fe6ae6f2eb N.434 Optimized the DB queries. As an example, the query that shows the service catalog in the enhanced customer portal is now made of 5 nodes (at the class level) whereas it used to be made of 11 nodes... for the exact same results. This optimization impacts almost each queries built by iTop. The expected benefit can insignificant or not, depending on the cardinality of the data, the datamodel and the original OQL queries. We found one case where the query execution would apparently never end and it takes now less than a second. The risk with such a change is that is affects most of the queries built by iTop -requires testing!
SVN:trunk[4448]
2016-10-07 08:45:24 +00:00
Romain Quetiez
79d994acf7 N.444 ... fixing regression introduced in [r4438]
SVN:trunk[4441]
2016-10-04 13:15:33 +00:00
Romain Quetiez
9dbbb8e012 Fixed regression introduced in [r4415]: data model remains unchanged though the customizations have been successfully compiled (and taken into account in the DB). The fix consists in completing the implementation: the verb apc_cache_info was missing, preventing the cache from being reset by the setup. I took the opportunity to add other verbs of apc so as to make sure that the emulation be consistent with the emulation provided by older versions of apcu.
SVN:trunk[4440]
2016-10-03 12:47:13 +00:00
Romain Quetiez
17fafbf85b N.444 Two date picker icons (lifecycle shortcut to resolved state, or a datetime attribute on a link). Solved by a factorization of the widgets initialization so that the initialization be the same (must be idempotent)
SVN:trunk[4438]
2016-10-03 11:47:03 +00:00
Guillaume Lajarige
f3a6455ed8 Portal : Bug when adding item on the first LinkedSet of an edition form
SVN:trunk[4436]
2016-10-03 08:01:23 +00:00
Romain Quetiez
5336d9e965 Internal: improved the API robustness by throwing an exception as soon as DBObject::Set is called with a date badly formatted. This is in line with the assumption that internal DBObject values are always safe.
SVN:trunk[4435]
2016-09-30 14:22:13 +00:00
Romain Quetiez
97d11ba910 N.445 Specifying a date format (other than the default one) and allowing to create a user request in the resolved status results in an error when selecting the resolved status.
SVN:trunk[4433]
2016-09-30 14:16:35 +00:00
Guillaume Lajarige
2fa68a3abc Portal : Deadline attributes not displayed properly in ManageBrick
SVN:trunk[4431]
2016-09-30 12:50:32 +00:00
Guillaume Lajarige
a88365ca49 Resize on AttributeImage crashes when gd extension is not installed. Implemented a fallback so images are stored as is (original size) when gd extension is not available. A warning message is displayed during the setup.
SVN:trunk[4429]
2016-09-30 11:24:30 +00:00
Guillaume Lajarige
f3053c39c2 Portal : Allowed Organizations part for action rules.
SVN:trunk[4428]
2016-09-30 08:47:43 +00:00
Guillaume Lajarige
c7ac39b86a Portal : ManageBrick crashing when displaying an abstract class with child classes attributes
SVN:trunk[4426]
2016-09-30 07:12:16 +00:00
Guillaume Lajarige
88eece7188 Portal : Autocomplete bug with IE9 in forms
SVN:trunk[4424]
2016-09-29 10:16:30 +00:00
Romain Quetiez
b7d101cdfb Optimization: cache icons files (scan directories for the Icon selection edition widget) benefiting to each dashboard page (20% faster with PHP7 + OpCache + APCu)
SVN:trunk[4423]
2016-09-27 14:42:35 +00:00
Guillaume Lajarige
56ade2b44c Removed debug traces for AllowAllData
SVN:trunk[4422]
2016-09-27 13:04:16 +00:00
Romain Quetiez
2a38eb757d Code cleanup: remove unused code causing warnings with PHP7 (SQLBlock)
SVN:trunk[4421]
2016-09-27 12:41:52 +00:00
Guillaume Lajarige
954ba60611 Portal : Caching TWIG templates to improve performances
SVN:trunk[4420]
2016-09-27 12:29:25 +00:00
Guillaume Lajarige
f9a984f3e4 #1334 Portal : Sorting objects on BrowseBrick regarding the all classes' default order and not the first class' order only. (For example the services catalog might appear as sorted on the first column but not the second one)
SVN:trunk[4418]
2016-09-27 08:18:14 +00:00
Romain Quetiez
8fd9acb2ce N.440 Remote troubleshooting: when the REST/JSON API fails due to malformed utf8 characters, return a meaningful json error message (explain + debug information) instead of an empty response
SVN:trunk[4417]
2016-09-26 19:11:08 +00:00
Romain Quetiez
915a88afd4 N.441 Character "à" in a case log causing the REST/JSON API to fail if mbstring is not enabled
SVN:trunk[4416]
2016-09-26 18:45:32 +00:00
Romain Quetiez
c755b66d8c Optimization: make it less cumbersome to have the APC user cache enabled for PHP 5.5+. Prior to this enhancement, it used to require a compilation flag (for apcu) until PHP7... then it became mandatory to install a dedicated backward compatibility module named after apcu_bc. Now, having apcu installed and enabled is ENOUGH.
SVN:trunk[4415]
2016-09-26 15:54:41 +00:00
Romain Quetiez
2f8dc0fa0f Prerequisite for #1334. New API: DBObjectSet::SetOrderByClasses. Helper to sort on multicolumn queries (SELECT a, b FROM)
SVN:trunk[4413]
2016-09-23 15:05:06 +00:00
Guillaume Lajarige
a34747f893 Portal : Missing AllDataAllowed
SVN:trunk[4412]
2016-09-23 13:58:08 +00:00
Guillaume Lajarige
4dd6c813b1 Portal : Typo
SVN:trunk[4411]
2016-09-23 13:37:45 +00:00
Guillaume Lajarige
8e96094977 Portal : Allowed Organizations Part II. Made sur that the AllowAllData flag was passed everywhere it was necessary, only when it was necessary. This has been tested but needs MORE testing !
SVN:trunk[4409]
2016-09-22 09:30:12 +00:00
Guillaume Lajarige
dd41ebf861 Portal : Preserve debug parameter through urls
SVN:trunk[4408]
2016-09-22 09:24:07 +00:00
Guillaume Lajarige
f732df751c Portal : Renamed <ignore_allowed_organizations> to <ignore_silos> for a more generic aproch
SVN:trunk[4406]
2016-09-20 14:41:56 +00:00
Guillaume Lajarige
acfab8fb63 Portal : Allowed Organizations can now be applied on the portal scopes. Just set the <ignore_allowed_organizations> to true under the concerned <scope> tag.
SVN:trunk[4405]
2016-09-20 14:22:04 +00:00
Romain Quetiez
23193153c6 N°436 Core API: Correctly (mathematically!) handle the "allow all data" flag, with UNIONS and INTERSECTIONS. Requires testing
SVN:trunk[4404]
2016-09-19 13:08:32 +00:00
Romain Quetiez
a908dcd752 #1178 Internals: Object Update/Reload should never fail: as soon as a given object has been read in the current execution context, updating/reloading it is not an issue.
SVN:trunk[4402]
2016-09-15 10:01:32 +00:00
Romain Quetiez
b8a80cb267 #1325 Part III - Completing the fix by aligning the internal data structures of iTop... and possibly fix an issue (?) - Not recommended for a retrofit
SVN:trunk[4399]
2016-09-14 15:42:19 +00:00
Romain Quetiez
ad1412c7e2 #1325 Part II - Completing the fix by aligning the internal data structures of iTop... and possibly fix an issue (?) - Not recommended for a retrofit
SVN:trunk[4398]
2016-09-14 15:35:16 +00:00
Romain Quetiez
4bf51515be #1325 Could not declare an ext key to a subclass (view could not be created). This commit is minimalistic and aims at being retrofitted into the various branches of iTop. It will be followed by a second commit, which aims at completing the fix by aligning the internal data structures of iTop... and possibly fix an issue (?)
SVN:trunk[4397]
2016-09-14 15:24:03 +00:00
Guillaume Lajarige
05f97dd75f Portal : Optimized column load in ManageBrick and BrowseBrick to improve performances
SVN:trunk[4396]
2016-09-13 08:47:24 +00:00
Denis Flaven
90cab29a3c Enhancement:
- Add more debug traces (if 'synchro_trace' == 'save')
- Show debug traces (if any) at the bottom of the status page
- Protect against time differences between the MySQL server and the PHP server, when running 'synchro_import.php'

SVN:trunk[4394]
2016-09-12 12:47:40 +00:00
Romain Quetiez
445b488603 #1323 error.log polluted with the contents of each email sent (transport = PHPMail)
SVN:trunk[4392]
2016-09-07 12:40:12 +00:00
Romain Quetiez
7141e0f4b0 Fix for regression introduced in [r4384] and causing a blank screen when attempting to edit an object
SVN:trunk[4390]
2016-09-06 13:47:59 +00:00
Denis Flaven
ffc756e4a4 (regression from iTop 2.2.x) ExternalFields were not automatically reloaded when the corresponding external key changed.
SVN:trunk[4388]
2016-09-06 10:08:06 +00:00
Romain Quetiez
8bed267feb Fixed XSS vulnerability
SVN:trunk[4386]
2016-09-06 10:01:37 +00:00
Romain Quetiez
13933e488c Rich text editor: the Maximize button icon is missing if iTop is installed in a directory which name contains spaces
SVN:trunk[4384]
2016-09-06 09:35:28 +00:00
Guillaume Lajarige
d48f76e965 Portal : Added Location scope to standard portal configuration because of the implementation of r4380
SVN:trunk[4382]
2016-09-06 09:30:10 +00:00
Guillaume Lajarige
a025c95054 Portal : External keys OQL now intersect with scopes in forms!
SVN:trunk[4380]
2016-09-06 07:04:24 +00:00
Guillaume Lajarige
b2fa9c468f Portal : Added a new mode "apply_stimulus" for forms. This allows to add flags to attributes that are prompt during transitions.
SVN:trunk[4378]
2016-09-05 14:29:05 +00:00
Romain Quetiez
8feef7fd8a #1321 Losing table borders (notification templates and notes)
SVN:trunk[4376]
2016-09-05 13:01:08 +00:00
Guillaume Lajarige
8330f3d498 Portal : Request template OQL list fields marked as mandatory were not validated properly
SVN:trunk[4371]
2016-09-02 12:30:41 +00:00
Guillaume Lajarige
88e7bd225c Portal : Updated inline documentation of UserProfile brick's <fields /> tag for easiest comprehension.
SVN:trunk[4370]
2016-09-02 12:16:57 +00:00
Romain Quetiez
1b0f818c4b Forms (portal): fix the rendering of a TEXT AREA in read-only mode.
1) format=text -> several lines were displayed on a single line
2) format=html -> characters encoded twice

SVN:trunk[4369]
2016-09-02 12:11:16 +00:00
Guillaume Lajarige
32dc1225ca Portal : Fixed search on enum & finalclass as well as display value of enum and html images in lists. Also Fixed display of friendlyname in lists, which was not behaving well on abstract class when the it was composed of several fields in the child classes.
SVN:trunk[4364]
2016-09-01 10:29:04 +00:00
Guillaume Lajarige
2b35cf5047 Portal : IE9 fixes
- Remaining console.log() inthe field_set.js file
- Missing zoom-in / zoom-out mouse cursors on a object images (They are actually not available on IE9, so I put a pointing hand instead)
- Missing pointer cursors on CaseLog field collapsers

SVN:trunk[4362]
2016-08-31 15:59:09 +00:00
Denis Flaven
df66e28545 Bug fix: regression from 2.3.x: SOAP webservices were broken!
SVN:trunk[4360]
2016-08-31 14:56:21 +00:00
Romain Quetiez
0ab060c958 Improved the comments for access_mode in the config file
SVN:trunk[4359]
2016-08-31 08:22:56 +00:00
Denis Flaven
dfceef4ca6 Enhancement: protect RenameValueInDB from non-existent attributes.
SVN:trunk[4357]
2016-08-30 12:56:13 +00:00
Denis Flaven
49f82e6377 #1297: timezone configuration setting was inoperant.
SVN:trunk[4355]
2016-08-29 12:47:06 +00:00
Guillaume Lajarige
c5957f1146 Portal : Fixed a regression introduced by r4324 causing HTML entities on the browse brick.
SVN:trunk[4353]
2016-08-29 07:27:29 +00:00
Guillaume Lajarige
b357e7d7d6 Portal : Enhanced and refactored error feedback on ExternalKey / LinkedSet / CustomFields fields
SVN:trunk[4352]
2016-08-25 15:21:33 +00:00
Guillaume Lajarige
e7342b0eb8 Portal : Request template list fields now have the autocomplete option.
SVN:trunk[4351]
2016-08-25 13:52:41 +00:00
Guillaume Lajarige
f33f4e3406 Portal : Request template list fields now have the lookup/search option. (Autocomplete is still to be implemented!)
SVN:trunk[4350]
2016-08-25 13:11:34 +00:00
Romain Quetiez
3d57c720e0 Could not bulk import with the "final class" (interactive import).
SVN:trunk[4348]
2016-08-24 16:50:02 +00:00
Romain Quetiez
ef5d156b98 #1305 Issue with date/time inputs on Chrome: losing focus as soon as the date has been correctly typed, preventing the user from typing the time.
SVN:trunk[4346]
2016-08-23 18:43:41 +00:00
Guillaume Lajarige
8722447f2f Portal : Templates not working with OQL "list" fields. This only append when the field had too many items and was trying to render as an autocomplete.
SVN:trunk[4344]
2016-08-23 16:01:25 +00:00
Guillaume Lajarige
e6047dcbf5 Portal : Templates not working with OQL "list" fields. This only append when the field had too many items and was trying to render as an autocomplete.
SVN:trunk[4343]
2016-08-23 16:00:00 +00:00
Guillaume Lajarige
df1d10f1cb #1299 Portal : "Oops, could not load data" when creating request in Full ITIL instance when running PHP7. Cause was that PHP7 isn't able to understand the factory method invocation synthax, had to make it more simple with intermediate steps.
SVN:trunk[4336]
2016-08-23 11:23:12 +00:00
Guillaume Lajarige
d1dd60f928 #1281 Portal : Fixed a few hardcoded strings to dictionnaries
SVN:trunk[4335]
2016-08-23 09:25:13 +00:00
Guillaume Lajarige
7bea59fea1 Portal : Fixed a bug with external key as radio button in forms
SVN:trunk[4334]
2016-08-22 15:50:35 +00:00
Guillaume Lajarige
17703ce572 Portal : Fixed a bug on the default configuration that was displaying only UserRequest in the Closed requests brick instead of both UserRequest and Incident objects.
SVN:trunk[4333]
2016-08-22 14:55:57 +00:00
Guillaume Lajarige
6d556249aa #1284: Fixed portal issue when trying to re-open a ticket as a portal user. Cause was that the destination state had "must prompt" attributes that were all "read only" for the current user, making the entire form "read only" and therefore removing "submit" button. The user was the not able to complete the transition. Fix consists of skipping the form when all attributes are "read only" for the user.
Also :
- Refactored a portion of TWIG (Loader is now in an helper TWIG)
- Placed transition buttons to the right with the submit one as it was confusing

SVN:trunk[4332]
2016-08-22 14:51:10 +00:00
Guillaume Lajarige
16b5db448b #1281 : Service catalog brick had 2 hardcoded headers ("Service" and "Sous-Service")
SVN:trunk[4330]
2016-08-11 10:00:49 +00:00
Denis Flaven
8ef7f073b3 CSV export of audit results: pass the parameters as a POST since they may be too long to fit in the query string of the URL.
SVN:trunk[4328]
2016-08-11 09:44:31 +00:00
Denis Flaven
22c647e3f8 Cleanup a Notice message: align the prototype of DBDeleteSingleObject to the current one.
SVN:trunk[4326]
2016-08-11 09:38:14 +00:00
Denis Flaven
bf94dfa894 Bug fix: support the display of HTML fields in the lists in the new portal.
SVN:trunk[4324]
2016-08-11 09:28:23 +00:00
Guillaume Lajarige
43d3cfefb5 Portal : Removed console.log to prevent crashes on IE9
SVN:trunk[4323]
2016-08-11 08:55:38 +00:00
Denis Flaven
e0855093ea Cosmetics: Enlarge DateTime fields which were too narrow (the end of the time is not visible when editing).
SVN:trunk[4321]
2016-08-11 08:34:10 +00:00
Denis Flaven
c147062aaa Regression introduced after 2.3.0-beta [r4217]: broken links to donwload / display blobs.
SVN:trunk[4319]
2016-08-10 15:54:04 +00:00
Denis Flaven
480c2fab04 Performance optimization: do not load all the columns when it is not needed.
SVN:trunk[4317]
2016-08-10 14:56:59 +00:00
Denis Flaven
53fb619da1 Image upload inside CKEditor (via drag and drop) seems to be desactivated on IE9.
SVN:trunk[4315]
2016-08-05 10:18:22 +00:00
Denis Flaven
c0a7bbaa72 Remember that console.log breaks IE9 when the console is not open !!!
SVN:trunk[4313]
2016-08-05 10:05:08 +00:00
Denis Flaven
12b27778f5 Bug fix: properly disable the configuration editor in demo mode! (Regression)
SVN:trunk[4310]
2016-07-27 09:50:40 +00:00
Denis Flaven
babcd6a92a Increased version number of the module to 1.0.1 to reflect the change related to revision 4300 (support of custom controller in a brick).
SVN:trunk[4308]
2016-07-26 08:15:42 +00:00
Denis Flaven
d80d24c348 Disable PDF export if the PHP extension "GD" is not loaded.
SVN:trunk[4307]
2016-07-15 07:05:06 +00:00
Denis Flaven
2c78a91a00 Model factory: introduced a new variation of attribute _delta: delete_if_exists. Use this flag to delete a branch from the data model being hacked, without caring if it is already deleted (or non existing). This eases the burden of coping with different installation combinations.
SVN:trunk[4306]
2016-07-15 06:41:26 +00:00
Romain Quetiez
109e5dfe2c Releasing 2.3.1
SVN:trunk[4302]
2016-07-08 12:00:27 +00:00
Denis Flaven
93ff327b54 2.3.0 Regression: login_mode was broken !
SVN:trunk[4301]
2016-07-08 11:56:27 +00:00
Guillaume Lajarige
359c188089 Customer portal : Added possibility to give a controller action for a brick tile. This allows to use some logic in order to make a specific render relying for example on DB dataobjects
SVN:trunk[4300]
2016-07-08 09:32:43 +00:00
Denis Flaven
6682eafb4d Oops, missing on Czech translation file.
SVN:trunk[4297]
2016-07-07 09:48:13 +00:00
1126 changed files with 45866 additions and 87305 deletions

View File

@@ -79,7 +79,7 @@ class Html2Text {
// replace &nbsp; with spaces
$html = str_replace("&nbsp;", " ", $html);
$html = mb_str_replace("\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
$html = mb_str_replace("\xc2\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
$html = static::fixNewlines($html);

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
* Simple web page with no includes, header or fancy formatting, useful to
* generate HTML fragments when called by an AJAX method
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -53,7 +53,9 @@ class ajax_page extends WebPage implements iTabbedPage
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';
$this->m_sMenu = "";
}
utils::InitArchiveMode();
}
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
@@ -202,32 +204,16 @@ EOF
// Render the tabs in the page (if any)
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
// Additional UI widgets to be activated inside the ajax fragment ??
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
// Additional UI widgets to be activated inside the ajax fragment
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
{
$this->add_ready_script(
<<<EOF
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true
});
PrepareWidgets();
EOF
);
}
}
$s_captured_output = $this->ob_get_clean_safe();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
{

View File

@@ -27,7 +27,6 @@
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
require_once(APPROOT.'/application/sqlblock.class.inc.php');
require_once(APPROOT.'/application/audit.category.class.inc.php');
require_once(APPROOT.'/application/audit.rule.class.inc.php');
require_once(APPROOT.'/application/query.class.inc.php');

View File

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

View File

@@ -308,6 +308,43 @@ interface iPopupMenuExtension
* $param is null
*/
const MENU_USER_ACTIONS = 5;
/**
* Insert an item into the Action menu on an object item in an objects list in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line)
*/
const PORTAL_OBJLISTITEM_ACTIONS = 7;
/**
* Insert an item into the Action menu on an object details page in the portal
*
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed)
*/
const PORTAL_OBJDETAILS_ACTIONS = 8;
/**
* Insert an item into the Actions menu of a list in the portal
* Note: This is not implemented yet !
*
* $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects
* @todo
*/
const PORTAL_OBJLIST_ACTIONS = 6;
/**
* Insert an item into the user menu of the portal
* Note: This is not implemented yet !
*
* $param is the portal id
* @todo
*/
const PORTAL_USER_ACTIONS = 9;
/**
* Insert an item into the navigation menu of the portal
* Note: This is not implemented yet !
*
* $param is the portal id
* @todo
*/
const PORTAL_MENU_ACTIONS = 10;
/**
* Get the list of items to be added to a menu.
@@ -334,17 +371,21 @@ abstract class ApplicationPopupMenuItem
protected $sUID;
/** @ignore */
protected $sLabel;
/** @ignore */
protected $aCssClasses;
/**
* Constructor
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
*/
* @param string $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)
{
$this->sUID = $sUID;
$this->sLabel = $sLabel;
$this->aCssClasses = array();
}
/**
@@ -368,6 +409,35 @@ abstract class ApplicationPopupMenuItem
{
return $this->sLabel;
}
/**
* Get the CSS classes
*
* @return array
* @ignore
*/
public function GetCssClasses()
{
return $this->aCssClasses;
}
/**
* @param $aCssClasses
*/
public function SetCssClasses($aCssClasses)
{
$this->aCssClasses = $aCssClasses;
}
/**
* Adds a CSS class to the CSS classes that will be put on the menu item
*
* @param $sCssClass
*/
public function AddCssClass($sCssClass)
{
$this->aCssClasses[] = $sCssClass;
}
/**
* Returns the components to create a popup menu item in HTML
@@ -415,7 +485,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */
public function GetMenuItem()
{
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget);
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses);
}
}
@@ -451,7 +521,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
public function GetMenuItem()
{
// Note: the semicolumn is a must here!
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#');
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#', 'css_classes' => $this->aCssClasses);
}
/** @ignore */
@@ -483,10 +553,34 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */
public function GetMenuItem()
{
return array ('label' => '<hr class="menu-separator">', 'url' => '');
return array ('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses);
}
}
/**
* Class for adding an item as a button that browses to the given URL
*
* @package Extensibility
* @api
* @since 2.0
*/
class URLButtonItem extends URLPopupMenuItem
{
}
/**
* Class for adding an item as a button that runs some JS code
*
* @package Extensibility
* @api
* @since 2.0
*/
class JSButtonItem extends JSPopupMenuItem
{
}
/**
* Implement this interface to add content to any iTopWebPage
*
@@ -528,6 +622,128 @@ interface iPageUIExtension
public function GetBannerHtml(iTopWebPage $oPage);
}
/**
* Implement this interface to add content to any enhanced portal page
*
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
*
* @package Extensibility
* @api
* @since 2.4
*/
interface iPortalUIExtension
{
const ENUM_PORTAL_EXT_UI_BODY = 'Body';
const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
/**
* Returns an array of CSS file urls
*
* @param \Silex\Application $oApp
* @return array
*/
public function GetCSSFiles(\Silex\Application $oApp);
/**
* Returns inline (raw) CSS
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetCSSInline(\Silex\Application $oApp);
/**
* Returns an array of JS file urls
*
* @param \Silex\Application $oApp
* @return array
*/
public function GetJSFiles(\Silex\Application $oApp);
/**
* Returns raw JS code
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetJSInline(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the <body> tag
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetBodyHTML(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the #main-wrapper element
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetMainContentHTML(\Silex\Application $oApp);
/**
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
*
* @param \Silex\Application $oApp
* @return string
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp);
}
/**
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
*/
abstract class AbstractPortalUIExtension implements iPortalUIExtension
{
/**
* @inheritDoc
*/
public function GetCSSFiles(\Silex\Application $oApp)
{
return array();
}
/**
* @inheritDoc
*/
public function GetCSSInline(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetJSFiles(\Silex\Application $oApp)
{
return array();
}
/**
* @inheritDoc
*/
public function GetJSInline(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetBodyHTML(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetMainContentHTML(\Silex\Application $oApp)
{
return null;
}
/**
* @inheritDoc
*/
public function GetNavigationMenuHTML(\Silex\Application $oApp)
{
return null;
}
}
/**
* Implement this interface to add new operations to the REST/JSON web service
*

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@ require_once(APPROOT.'core/modelreflection.class.inc.php');
/**
* A user editable dashboard page
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class Dashboard
@@ -515,8 +515,6 @@ class RuntimeDashboard extends Dashboard
// Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch();
$oUserDashboard->Set('contents', $sXml);
$oUserDashboard->DBUpdate();
}
else
{
@@ -525,9 +523,10 @@ class RuntimeDashboard extends Dashboard
$oUserDashboard->Set('user_id', UserRights::GetUserId());
$oUserDashboard->Set('menu_code', $this->sId);
$oUserDashboard->Set('contents', $sXml);
$oUserDashboard->DBInsert();
}
}
utils::PushArchiveMode(false);
$oUserDashboard->DBWrite();
utils::PopArchiveMode();
}
public function Revert()
@@ -540,7 +539,9 @@ class RuntimeDashboard extends Dashboard
{
// Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch();
utils::PushArchiveMode(false);
$oUserDashboard->DBDelete();
utils::PopArchiveMode();
}
}
@@ -754,33 +755,65 @@ EOF
public static function GetDashletCreationForm($sOQL = null)
{
$oAppContext = new ApplicationContext();
$sContextMenuId = $oAppContext->GetCurrentValue('menu', null);
$oForm = new DesignerForm();
// Get the list of all 'dashboard' menus in which we can insert a dashlet
$aAllMenus = ApplicationMenu::ReflectionMenuNodes();
$sRootMenuId = ApplicationMenu::GetRootMenuId($sContextMenuId);
$aAllowedDashboards = array();
foreach($aAllMenus as $idx => $aMenu)
$sDefaultDashboard = null;
// Store the parent menus for acces check
$aParentMenus = array();
foreach($aAllMenus as $idx => $aMenu)
{
/** @var MenuNode $oMenu */
$oMenu = $aMenu['node'];
if (count(ApplicationMenu::GetChildren($oMenu->GetIndex())) > 0)
{
$aParentMenus[$oMenu->GetMenuId()] = $aMenu;
}
}
foreach($aAllMenus as $idx => $aMenu)
{
$oMenu = $aMenu['node'];
$sParentId = $aMenu['parent'];
if ($oMenu instanceof DashboardMenuNode)
{
$sMenuLabel = $oMenu->GetTitle();
$sParentLabel = Dict::S('Menu:'.$sParentId);
if ($sParentLabel != $sMenuLabel)
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
}
else
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
}
}
if ($oMenu instanceof DashboardMenuNode)
{
// Get the root parent for access check
$sParentId = $aMenu['parent'];
$aParentMenu = $aParentMenus[$sParentId];
while (isset($aParentMenus[$aParentMenu['parent']]))
{
// grand parent exists
$sParentId = $aParentMenu['parent'];
$aParentMenu = $aParentMenus[$sParentId];
}
$oParentMenu = $aParentMenu['node'];
if ($oMenu->IsEnabled() && $oParentMenu->IsEnabled())
{
$sMenuLabel = $oMenu->GetTitle();
$sParentLabel = Dict::S('Menu:'.$sParentId);
if ($sParentLabel != $sMenuLabel)
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
}
else
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
}
if (empty($sDefaultDashboard) && ($sRootMenuId == ApplicationMenu::GetRootMenuId($oMenu->GetMenuId())))
{
$sDefaultDashboard = $oMenu->GetMenuId();
}
}
}
}
asort($aAllowedDashboards);
$aKeys = array_keys($aAllowedDashboards); // Select the first one by default
$sDefaultDashboard = $aKeys[0];
$oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard);
$oField->SetAllowedValues($aAllowedDashboards);
$oField->SetMandatory(true);
@@ -841,7 +874,7 @@ EOF
$oPage->add_ready_script(
<<<EOF
$('#dashlet_creation_dlg').dialog({
width: 400,
width: 600,
modal: true,
title: '$sDialogTitle',
buttons: [

View File

@@ -104,7 +104,7 @@ 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%"><tbody>');
$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.'%;';

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2012-2013 Combodo SARL
// Copyright (C) 2012-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@ require_once(APPROOT.'application/forms.class.inc.php');
/**
* Base class for all 'dashlets' (i.e. widgets to be inserted into a dashboard)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class Dashlet
@@ -530,7 +530,7 @@ abstract class DashletGroupBy extends Dashlet
$this->sGroupByAttCode = $sGroupBy;
$this->sFunction = null;
}
if ($this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
if (($sClass != '') && $this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
{
$sAttLabel = $this->oModelReflection->GetLabel($sClass, $this->sGroupByAttCode);
if (!is_null($this->sFunction))
@@ -583,6 +583,7 @@ abstract class DashletGroupBy extends Dashlet
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
$oFilter = DBObjectSearch::FromOQL($sQuery);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$sClass = $oFilter->GetClass();
$sClassAlias = $oFilter->GetClassAlias();
@@ -685,7 +686,7 @@ abstract class DashletGroupBy extends Dashlet
}
foreach ($aValues as $sValue)
{
$aDisplayValues[] = array('label' => $sValue, 'count' => (int)rand(1, 15));
$aDisplayValues[] = array('label' => $sValue, 'value' => (int)rand(1, 15));
}
}
elseif (is_subclass_of($sAttributeType, 'AttributeEnum') || $sAttributeType == 'AttributeEnum')
@@ -698,16 +699,16 @@ abstract class DashletGroupBy extends Dashlet
$iCount = (int) rand(2, 100);
$aDisplayValues[] = array(
'label' => $sValueLabel,
'count' => $iCount
'value' => $iCount
);
}
}
}
else
{
$aDisplayValues[] = array('label' => 'a', 'count' => 123);
$aDisplayValues[] = array('label' => 'b', 'count' => 321);
$aDisplayValues[] = array('label' => 'c', 'count' => 456);
$aDisplayValues[] = array('label' => 'a', 'value' => 123);
$aDisplayValues[] = array('label' => 'b', 'value' => 321);
$aDisplayValues[] = array('label' => 'c', 'value' => 456);
}
}
return $aDisplayValues;
@@ -905,82 +906,42 @@ class DashletGroupByPie extends DashletGroupBy
{
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$aDisplayValues = $this->MakeSimulatedData();
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
$oChart = new open_flash_chart();
$aGroupBy = array();
$aLabels = array();
foreach($aDisplayValues as $iRow => $aDisplayData)
$aColumns = array();
$aNames = array();
foreach($aDisplayValues as $idx => $aValue)
{
$aLabels[$iRow] = $aDisplayData['label'];
$aGroupBy[$iRow] = (int) $aDisplayData['count'];
$aColumns[] = array('series_'.$idx, (int)$aValue['value']);
$aNames['series_'.$idx] = $aValue['label'];
}
$oChartElement = new pie();
$oChartElement->set_start_angle( 35 );
$oChartElement->set_animate( true );
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
$aData = array();
foreach($aGroupBy as $iRow => $iCount)
{
$sFlashLabel = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
$PieValue = new pie_value($iCount, $sFlashLabel);
$aData[] = $PieValue;
}
$oChartElement->set_values($aData);
$oChart->x_axis = null;
if (!empty($sTitle))
{
// The title has been given in an url, and urlencoded...
// and urlencode transforms utf-8 into something similar to ISO-8859-1
// Example: é (C3A9 becomes %E9)
// As a consequence, json_encode (called within open-flash-chart.php)
// was returning 'null' and the graph was not displayed at all
// To make sure that the graph is displayed AND to get a correct title
// (at least for european characters) let's transform back into utf-8 !
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $sTitle);
// If the title is a dictionnary entry, fetch it
$sTitle = $this->oModelReflection->DictString($sTitle);
$oTitle = new title($sTitle);
$oChart->set_title($oTitle);
$oTitle->set_style("{font-size: 16px; font-family: Tahoma; font-weight: bold; text-align: center;}");
}
$oChart->set_bg_colour('#FFFFFF');
$oChart->add_element($oChartElement);
$sData = $oChart->toPrettyString();
$sData = json_encode($sData);
$oPage->add_script(
<<< EOF
function ofc_get_data_dashlet_{$this->sId}()
{
return $sData;
}
EOF
);
$oPage->add('<div class="dashlet-content">');
$oPage->add("<div id=\"dashlet_chart_{$this->sId}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n");
$oPage->add('</div>');
// $oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
$sJSColumns = json_encode($aColumns);
$sJSNames = json_encode($aNames);
$oPage->add_ready_script(
<<<EOF
swfobject.embedSWF( "../images/open-flash-chart.swf",
"dashlet_chart_{$this->sId}",
"100%", "300","9.0.0",
"expressInstall.swf",
{"get-data":"ofc_get_data_dashlet_{$this->sId}", "id":"dashlet_chart_{$this->sId}"},
{'wmode': 'transparent'}
);
window.setTimeout(function() {
var chart = c3.generate({
bindto: '#{$sBlockId}',
data: {
columns: $sJSColumns,
type: 'pie',
names: $sJSNames,
},
legend: {
show: true,
position: 'right',
},
tooltip: {
format: {
value: function (value, ratio, id) { return value; }
}
}
});}, 100);
EOF
);
}
@@ -1008,106 +969,69 @@ class DashletGroupByBars extends DashletGroupBy
{
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$aDisplayValues = $this->MakeSimulatedData();
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
$oChart = new open_flash_chart();
$aGroupBy = array();
$aLabels = array();
foreach($aDisplayValues as $iRow => $aDisplayData)
$aNames = array();
foreach($aDisplayValues as $idx => $aValue)
{
$aLabels[$iRow] = $aDisplayData['label'];
$aGroupBy[$iRow] = (int) $aDisplayData['count'];
$aNames[$idx] = $aValue['label'];
}
$oChartElement = new bar_glass();
$aData = array();
$aChartLabels = array();
$maxValue = 0;
foreach($aGroupBy as $iRow => $iCount)
{
$oBarValue = new bar_value($iCount);
$aData[] = $oBarValue;
if ($iCount > $maxValue) $maxValue = $iCount;
$aChartLabels[] = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
}
$oYAxis = new y_axis();
$aMagicValues = array(1,2,5,10);
$iMultiplier = 1;
$index = 0;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
while($maxValue > $iTop)
{
$index++;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
if (($index % count($aMagicValues)) == 0)
{
$iMultiplier = $iMultiplier * 10;
}
}
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
$oYAxis->set_range(0, $iTop, $iMultiplier);
$oChart->set_y_axis( $oYAxis );
$oChartElement->set_values( $aData );
$oXAxis = new x_axis();
$oXLabels = new x_axis_labels();
// set them vertical
$oXLabels->set_vertical();
// set the label text
$oXLabels->set_labels($aChartLabels);
// Add the X Axis Labels to the X Axis
$oXAxis->set_labels( $oXLabels );
$oChart->set_x_axis( $oXAxis );
if (!empty($sTitle))
{
// The title has been given in an url, and urlencoded...
// and urlencode transforms utf-8 into something similar to ISO-8859-1
// Example: é (C3A9 becomes %E9)
// As a consequence, json_encode (called within open-flash-chart.php)
// was returning 'null' and the graph was not displayed at all
// To make sure that the graph is displayed AND to get a correct title
// (at least for european characters) let's transform back into utf-8 !
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $sTitle);
$sJSNames = json_encode($aNames);
// If the title is a dictionnary entry, fetch it
$sTitle = $this->oModelReflection->DictString($sTitle);
$oTitle = new title($sTitle);
$oChart->set_title($oTitle);
$oTitle->set_style("{font-size: 16px; font-family: Tahoma; font-weight: bold; text-align: center;}");
}
$oChart->set_bg_colour('#FFFFFF');
$oChart->add_element($oChartElement);
$sData = $oChart->toPrettyString();
$sData = json_encode($sData);
$oPage->add_script(
<<< EOF
function ofc_get_data_dashlet_{$this->sId}()
{
return $sData;
}
EOF
);
$oPage->add('<div class="dashlet-content">');
$oPage->add("<div id=\"dashlet_chart_{$this->sId}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n");
$oPage->add('</div>');
// $oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
$sJson = json_encode($aDisplayValues);
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
$oPage->add_ready_script(
<<<EOF
swfobject.embedSWF( "../images/open-flash-chart.swf",
"dashlet_chart_{$this->sId}",
"100%", "300","9.0.0",
"expressInstall.swf",
{"get-data":"ofc_get_data_dashlet_{$this->sId}", "id":"dashlet_chart_{$this->sId}"},
{'wmode': 'transparent'}
);
window.setTimeout(function() {
var chart = c3.generate({
bindto: '#{$sBlockId}',
data: {
json: $sJson,
keys: {
x: 'label',
value: ["value"]
},
selection: {
enabled: true
},
type: 'bar'
},
axis: {
x: {
tick: {
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
centered: true,
rotate: 90,
multiline: false
},
type: 'category' // this needed to load string x value
}
},
grid: {
y: {
show: true
}
},
legend: {
show: false,
},
tooltip: {
grouped: false,
format: {
title: function() { return '' },
name: function (name, ratio, id, index) {
var aNames = $sJSNames;
return aNames[index];
}
}
}
});
}, 100);
EOF
);
}
@@ -1138,7 +1062,7 @@ class DashletGroupByTable extends DashletGroupBy
$iTotal = 0;
foreach($aDisplayValues as $iRow => $aDisplayData)
{
$iTotal += $aDisplayData['count'];
$iTotal += $aDisplayData['value'];
}
$oPage->add('<div class="dashlet-content">');
@@ -1159,7 +1083,7 @@ class DashletGroupByTable extends DashletGroupBy
{
$oPage->add('<tr class="even">');
$oPage->add('<td class=""><span title="Active">'.$aDisplayData['label'].'</span></td>');
$oPage->add('<td class=""><a>'.$aDisplayData['count'].'</a></td>');
$oPage->add('<td class=""><a>'.$aDisplayData['value'].'</a></td>');
$oPage->add('</tr>');
}
$oPage->add('</tbody>');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* Data Table to display a set of objects in a tabular manner in HTML
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -31,7 +31,8 @@ class DataTable
protected $iNbObjects; // Total number of objects inthe set
protected $bUseCustomSettings; // Whether or not the current display uses custom settings
protected $oDefaultSettings; // the default settings for displaying such a list
protected $bShowObsoleteData;
/**
* @param $iListId mixed Unique ID for this div/table in the page
* @param $oSet DBObjectSet The set of data to display
@@ -47,6 +48,7 @@ class DataTable
$this->iNbObjects = $oSet->Count();
$this->bUseCustomSettings = false;
$this->oDefaultSettings = null;
$this->bShowObsoleteData = $oSet->GetShowObsoleteData();
}
public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
@@ -145,7 +147,9 @@ class DataTable
$sHtml .= "<tr><td class=\"datacontents\">$sDataTable</td></tr>";
$sHtml .= "</table>\n";
$oPage->add_at_the_end($sConfigDlg);
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
$aOptions = array(
'sPersistentId' => '',
'sFilter' => $this->oSet->GetFilter()->serialize(),
@@ -486,6 +490,7 @@ EOF;
{
$aExtraParams['query_params'][$sName] = $sValue;
}
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
$sHtml .= "<tr><td>";
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
@@ -928,8 +933,15 @@ class DataTableSettings implements Serializable
}
else if ($oAttDef->IsExternalField())
{
$oExtAttDef = $oAttDef->GetExtAttDef();
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
if ($oAttDef->IsFriendlyName())
{
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
}
else
{
$oExtAttDef = $oAttDef->GetExtAttDef();
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
}
}
elseif ($oAttDef instanceof AttributeFriendlyName)
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* DisplayBlock and derived class
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -49,6 +49,7 @@ class DisplayBlock
protected $m_bAsynchronous;
protected $m_aParams;
protected $m_oSet;
protected $m_bShowObsoleteData = null;
public function __construct(DBSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null)
{
@@ -58,6 +59,15 @@ class DisplayBlock
$this->m_bAsynchronous = $bAsynchronous;
$this->m_aParams = $aParams;
$this->m_oSet = $oSet;
if (array_key_exists('show_obsolete_data', $aParams))
{
$this->m_bShowObsoleteData = $aParams['show_obsolete_data'];
}
if ($this->m_bShowObsoleteData === null)
{
// User defined
$this->m_bShowObsoleteData = utils::ShowObsoleteData();
}
}
public function GetFilter()
@@ -390,6 +400,7 @@ class DisplayBlock
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
switch($this->m_sStyle)
{
case 'count':
@@ -650,7 +661,7 @@ class DisplayBlock
case 'links':
//$bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false;
//$bSelectMode = isset($aExtraParams['select']) ? ($aExtraParams['select'] == 'true') : false;
if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
if ( ($this->m_oSet->Count(1)> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
{
//$sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : '';
$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams);
@@ -709,7 +720,8 @@ class DisplayBlock
{
$aQueryParams = $aExtraParams['query_params'];
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$iCount = $this->m_oSet->Count();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize());
@@ -752,7 +764,8 @@ class DisplayBlock
{
$aQueryParams = $aExtraParams['query_params'];
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
}
// Summary details
$aCounts = array();
@@ -761,20 +774,38 @@ class DisplayBlock
{
$aStates = explode(',', $sStatesList);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
// Generate one count + group by query [#1330]
$sClassAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode);
$aGroupBy = array('group1' => $oGroupByExpr);
$sCountGroupByQuery = $this->m_oFilter->MakeGroupByQuery(array(), $aGroupBy, false);
$aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery);
$aCountsQueryResults = array();
foreach ($aCountGroupByResults as $aCountGroupBySingleResult)
{
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
}
foreach($aStates as $sStateValue)
{
$oFilter = $this->m_oFilter->DeepClone();
$oFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
$oSet = new DBObjectSet($oFilter);
$aCounts[$sStateValue] = $oSet->Count();
$aStateLabels[$sStateValue] = htmlentities($oAttDef->GetValueLabel($sStateValue), ENT_QUOTES, 'UTF-8');
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
? $aCountsQueryResults[$sStateValue]
: 0;
if ($aCounts[$sStateValue] == 0)
{
$aCounts[$sStateValue] = '-';
}
else
{
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($oFilter->serialize());
$oSingleGroupByValueFilter = $this->m_oFilter->DeepClone();
$oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
$sHyperlink = utils::GetAbsoluteUrlAppRoot()
.'pages/UI.php?operation=search&'.$oAppContext->GetForLink()
.'&filter='.urlencode($oSingleGroupByValueFilter->serialize());
$aCounts[$sStateValue] = "<a href=\"$sHyperlink\">{$aCounts[$sStateValue]}</a>";
}
}
@@ -795,9 +826,17 @@ class DisplayBlock
$sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($sCsvFile);
$sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize()).'&format=csv';
// Pass the parameters via POST, since expression may be very long
$aParamsToPost = array(
'expression' => $this->m_oFilter->ToOQL(true),
'format' => 'csv',
'filename' => $sCsvFile,
'charset' => 'UTF-8',
);
if ($bAdvancedMode)
{
$sDownloadLink .= '&fields_advanced=1';
$aParamsToPost['fields_advance'] = 1;
$sChecked = 'CHECKED';
}
else
@@ -805,7 +844,7 @@ class DisplayBlock
$sLinkToToggle = $sLinkToToggle.'&advanced=1';
$sChecked = '';
}
$sAjaxLink = $sDownloadLink.'&charset=UTF-8'; // Includes &fields_advanced=1 if in advanced mode
$sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
/*
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
@@ -856,7 +895,8 @@ class DisplayBlock
$sHtml .= "<div id=\"csv_content_loading\"><div style=\"width: 250px; height: 20px; background: url(../setup/orange-progress.gif); border: 1px #999 solid; margin-left:auto; margin-right: auto; text-align: center;\">".Dict::S('UI:Loading')."</div></div><textarea id=\"csv_content\" style=\"display:none;\">\n";
//$sHtml .= htmlentities($sCSVData, ENT_QUOTES, 'UTF-8');
$sHtml .= "</textarea>\n";
$oPage->add_ready_script("$.post('$sAjaxLink', {}, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
$sJsonParams = json_encode($aParamsToPost);
$oPage->add_ready_script("$.post('$sAjaxLink', $sJsonParams, function(data) { $('#csv_content').html(data); $('#csv_content_loading').hide(); $('#csv_content').show();} );");
break;
case 'modify':
@@ -951,7 +991,6 @@ EOF
$sContextParam = $oContext->GetForLink();
$aGroupBy = array();
$aLabels = array();
$iTotalCount = 0;
$aValues = array();
$aURLs = array();
@@ -959,7 +998,6 @@ EOF
{
$sValue = $aRow['grouped_by_1'];
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = strip_tags($sHtmlValue);
$aGroupBy[(int)$iRow] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow['_itop_count_']);
@@ -979,7 +1017,7 @@ EOF
$aNames = array();
foreach($aValues as $idx => $aValue)
{
$aNames[$idx] = $aValue['label_html'];
$aNames[$idx] = $aValue['label'];
}
$sJSNames = json_encode($aNames);
@@ -1006,6 +1044,12 @@ var chart = c3.generate({
},
axis: {
x: {
tick: {
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
centered: true,
rotate: 90,
multiline: false
},
type: 'category' // this needed to load string x value
}
},
@@ -1057,6 +1101,7 @@ var chart = c3.generate({
},
legend: {
show: true,
position: 'right',
},
tooltip: {
format: {
@@ -1339,6 +1384,12 @@ class MenuBlock extends DisplayBlock
$aActions = array();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
// Common params that will be applied to actions
$aActionParams = array();
if(isset($aExtraParams['menu_actions_target']))
{
$aActionParams['target'] = $aExtraParams['menu_actions_target'];
}
// 1:n links, populate the target object as a default value when creating a new linked object
if (isset($aExtraParams['target_attr']))
{
@@ -1358,7 +1409,7 @@ class MenuBlock extends DisplayBlock
{
case 0:
// No object in the set, the only possible action is "new"
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; }
break;
case 1:
@@ -1367,7 +1418,7 @@ class MenuBlock extends DisplayBlock
{
if (!isset($aExtraParams['link_attr']))
{
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; }
}
}
else
@@ -1402,9 +1453,9 @@ class MenuBlock extends DisplayBlock
// Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr']))
{
if ($bIsModifyAllowed) { $aActions['UI:Menu:Modify'] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsDeleteAllowed) { $aActions['UI:Menu:Delete'] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}"); }
if ($bIsModifyAllowed) { $aActions['UI:Menu:Modify'] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#") + $aActionParams; }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; }
if ($bIsDeleteAllowed) { $aActions['UI:Menu:Delete'] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}") + $aActionParams; }
// Transitions / Stimuli
if (!$bLocked)
{
@@ -1419,7 +1470,7 @@ class MenuBlock extends DisplayBlock
switch($iActionAllowed)
{
case UR_ALLOWED_YES:
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}");
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}") + $aActionParams;
break;
default:
@@ -1437,11 +1488,11 @@ class MenuBlock extends DisplayBlock
{
if (array_key_exists('down', $aRelationInfo))
{
$aActions[$sRelationCode.'_down'] = array ('label' => $aRelationInfo['down'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}");
$aActions[$sRelationCode.'_down'] = array ('label' => $aRelationInfo['down'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}") + $aActionParams;
}
if (array_key_exists('up', $aRelationInfo))
{
$aActions[$sRelationCode.'_up'] = array ('label' => $aRelationInfo['up'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}");
$aActions[$sRelationCode.'_up'] = array ('label' => $aRelationInfo['up'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}") + $aActionParams;
}
}
}
@@ -1498,7 +1549,7 @@ class MenuBlock extends DisplayBlock
$oSet->Rewind();
foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl)
{
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl);
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl) + $aActionParams;
}
}
}
@@ -1517,23 +1568,23 @@ class MenuBlock extends DisplayBlock
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
$sTargetClass = $oAttDef->GetTargetClass();
$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
if ($bIsModifyAllowed) { $aActions['UI:Menu:Add'] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:Manage'] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}"); }
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#"); }
if ($bIsModifyAllowed) { $aActions['UI:Menu:Add'] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}") + $aActionParams; }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:Manage'] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}") + $aActionParams; }
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; }
}
else
{
// many objects in the set, possible actions are: new / modify all / delete all
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:ModifyAll'] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}"); }
if ($bIsBulkDeleteAllowed) { $aActions['UI:Menu:BulkDelete'] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:ModifyAll'] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}") + $aActionParams; }
if ($bIsBulkDeleteAllowed) { $aActions['UI:Menu:BulkDelete'] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}") + $aActionParams; }
// Stimuli
$aStates = MetaModel::EnumStates($sClass);
// Do not perform time consuming computations if there are too may objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->Count() < $iLimit)))
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->Count($iLimit + 1) < $iLimit)))
{
// Life cycle actions may be available... if all objects are in the same state
//
@@ -1567,7 +1618,7 @@ class MenuBlock extends DisplayBlock
{
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}");
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}") + $aActionParams;
break;
default:
@@ -1603,7 +1654,7 @@ class MenuBlock extends DisplayBlock
else
{
// Backward compatibility with old plugins
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $data);
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $data) + $aActionParams;
}
}
}

View File

@@ -360,6 +360,7 @@ EOF
<<<EOF
$('#$sDialogId').dialog({
height: 'auto',
maxHeight: $(window).height() - 8,
width: $iDialogWidth,
modal: true,
autoOpen: $sAutoOpen,
@@ -531,7 +532,7 @@ EOF
public function GetFieldId($sCode)
{
return $this->GetPrefix().'attr_'.$sCode;
return $this->GetPrefix().'attr_'.utils::GetSafeId($sCode.$this->GetSuffix());
}
public function GetFieldName($sCode)
@@ -881,7 +882,7 @@ class DesignerTextField extends DesignerFormField
$this->sValidationPattern = $sValidationPattern;
}
public function SetForbiddenValues($aValues, $sExplain)
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
{
$aForbiddenValues = $aValues;
@@ -893,7 +894,7 @@ class DesignerTextField extends DesignerFormField
}
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain);
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain, 'case_sensitive' => $bCaseSensitive);
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
@@ -1367,6 +1368,36 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
}
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
{
$aFiles = null;
$sKey = $sBaseDir.'/'.$sDir;
$sShortKey = abs(crc32($sKey));
$sCacheFile = utils::GetCachePath().'available-icons-'.$sShortKey.'.php';
$sCacheClass = 'AvailableIcons_'.$sShortKey;
if (file_exists($sCacheFile))
{
require_once($sCacheFile);
if ($sCacheClass::$sKey === $sKey) // crc32 collision detection
{
$aFiles = $sCacheClass::$aIconFiles;
}
}
if ($aFiles === null)
{
$aFiles = self::_FindIconsOnDisk($sBaseDir, $sDir);
$sAvailableIcons = '<?php'.PHP_EOL;
$sAvailableIcons .= '// Generated and used by '.__METHOD__.PHP_EOL;
$sAvailableIcons .= 'class '.$sCacheClass.PHP_EOL;
$sAvailableIcons .= '{'.PHP_EOL;
$sAvailableIcons .= ' static $sKey = '.var_export($sKey, true).';'.PHP_EOL;
$sAvailableIcons .= ' static $aIconFiles = '.var_export($aFiles, true).';'.PHP_EOL;
$sAvailableIcons .= '}'.PHP_EOL;
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
}
return $aFiles;
}
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
{
$aResult = array();
// Populate automatically the list of icon files
@@ -1378,7 +1409,7 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
{
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
$aResult = array_merge($aResult, self::FindIconsOnDisk($sBaseDir, $sDirSubPath));
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
}
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
{
@@ -1408,8 +1439,12 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
public function GetDefaultValue($sClass = 'Contact')
{
$sIconPath = MetaModel::GetClassIcon($sClass, false);
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
$sIcon = '';
if (MetaModel::IsValidClass($sClass))
{
$sIconPath = MetaModel::GetClassIcon($sClass, false);
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
}
return $sIcon;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class iTopWebPage
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -34,7 +34,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
{
private $m_sMenu;
// private $m_currentOrganization;
private $m_sMessage;
private $m_aMessages;
private $m_sInitScript;
protected $m_oTabs;
protected $bBreadCrumbEnabled;
@@ -63,8 +63,10 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->bBreadCrumbEnabled = false;
}
utils::InitArchiveMode();
$this->m_sMenu = "";
$this->m_sMessage = '';
$this->m_aMessages = array();
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header("Cache-control: no-cache");
@@ -75,6 +77,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
$this->add_linked_stylesheet("../css/magnific-popup.css");
$this->add_linked_stylesheet("../css/c3.min.css");
$this->add_linked_stylesheet("../css/font-awesome/css/font-awesome.min.css");
$this->add_linked_script('../js/jquery.layout.min.js');
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
@@ -121,6 +124,23 @@ function ShowAboutBox()
});
return false;
}
function ArchiveMode(bEnable)
{
var sPrevUrl = StripArchiveArgument(window.location.search);
if (bEnable)
{
window.location.search = sPrevUrl + '&with-archive=1';
}
else
{
window.location.search = sPrevUrl + '&with-archive=0';
}
}
function StripArchiveArgument(sUrl)
{
var res = sUrl.replace(/&with-archive=[01]/g, '');
return res;
}
EOF
);
}
@@ -191,7 +211,8 @@ EOF;
$sJSDatePickerOptions = json_encode($aPickerOptions);
// Time picker additional options
$aPickerOptions['showOn'] = '';
$aPickerOptions['buttonImage'] = null;
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
$aPickerOptions['controlType'] = 'select';
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
@@ -208,7 +229,41 @@ EOF;
}";
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
}
$this->add_script(
<<< EOF
function PrepareWidgets()
{
// note: each action implemented here must be idempotent,
// because this helper function might be called several times on a given page
// Note: Trigger image is wrapped in a span so we can display it we want
$(".date-pick").datepicker($sJSDatePickerOptions)
.next("img").wrap("<span>");
// Hack for the date and time picker addon issue on Chrome (see #1305)
// The workaround is to instantiate the widget on demand
// It relies on the same markup, thus reverting to the original implementation should be straightforward
$(".datetime-pick:not(.is-widget-ready)").each(function(){
var oInput = this;
$(oInput).addClass('is-widget-ready');
$('<span><img class="datetime-pick-button" src="../images/calendar.png"></span>')
.insertAfter($(this))
.on('click', function(){
$(oInput)
.datetimepicker($sJSDateTimePickerOptions)
.datetimepicker('show')
.datetimepicker('option', 'onClose', function(dateText,inst){
$(oInput).datetimepicker('destroy');
})
.on('click keypress', function(){
$(oInput).datetimepicker('hide');
});
});
});
}
EOF
);
$this->m_sInitScript =
<<< EOF
try
@@ -275,7 +330,7 @@ EOF;
// This selector will be reused when selecting actual tab widget A elements.
var tab_a_selector = 'ul.ui-tabs-nav a';
// Ugly patch for a change in the behavior of jQuery UI:
// Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax)
// when their href was beginning by #. Starting with 1.9, a <base> tag in the page
@@ -444,8 +499,7 @@ EOF
// End of Tabs handling
$(".date-pick").datepicker($sJSDatePickerOptions);
$(".datetime-pick").datetimepicker($sJSDateTimePickerOptions);
PrepareWidgets();
// Make sortable, everything that claims to be sortable
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()
@@ -460,7 +514,7 @@ EOF
}
});
docWidth = $(document).width();
$('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs
$('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth, height: 'auto', maxHeight: $(window).height() - 50 }); // JQuery UI dialogs
ShowDebug();
$('#logOffBtn>ul').popupmenu();
@@ -700,7 +754,7 @@ EOF
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled())
{
$sNorthPane .= '<div id="admin-banner"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
$sNorthPane .= '<div class="app-message"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
}
//$sSouthPane = '<p>Peak memory Usage: '.sprintf('%.3f MB', memory_get_peak_usage(true) / (1024*1024)).'</p>';
@@ -908,12 +962,12 @@ EOF
if (ITOP_REVISION == '$WCREV$')
{
// This is NOT a version built using the buil system, just display the main version
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
}
else
{
// This is a build made from SVN, let display the full information
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
}
// Render the text of the global search form
@@ -948,9 +1002,39 @@ EOF
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
$aActions = array();
$aAllowedPortals = UserRights::GetAllowedPortals();
if(count($aAllowedPortals) > 1)
{
// Adding portals
foreach($aAllowedPortals as $aAllowedPortal)
{
if($aAllowedPortal['id'] !== 'backoffice')
{
$oPortalMenuItem = new URLPopupMenuItem('portal:'.$aAllowedPortal['id'], Dict::S($aAllowedPortal['label']), $aAllowedPortal['url'], '_blank');
$aActions[$oPortalMenuItem->GetUID()] = $oPortalMenuItem->GetMenuItem();
}
}
// Adding a separator
$oPortalSeparatorMenuItem = new SeparatorPopupMenuItem();
$aActions[$oPortalSeparatorMenuItem->GetUID()] = $oPortalSeparatorMenuItem->GetMenuItem();
}
$oPrefs = new URLPopupMenuItem('UI:Preferences', Dict::S('UI:Preferences'), utils::GetAbsoluteUrlAppRoot()."pages/preferences.php?".$oAppContext->GetForLink());
$aActions[$oPrefs->GetUID()] = $oPrefs->GetMenuItem();
if (utils::IsArchiveMode())
{
$oExitArchive = new JSPopupMenuItem('UI:ArchiveModeOff', Dict::S('UI:ArchiveModeOff'), 'return ArchiveMode(false);');
$aActions[$oExitArchive->GetUID()] = $oExitArchive->GetMenuItem();
$sIcon = '<span class="fa fa-lock fa-1x"></span>';
$this->AddApplicationMessage(Dict::S('UI:ArchiveMode:Banner'), $sIcon, Dict::S('UI:ArchiveMode:Banner+'));
}
elseif (UserRights::CanBrowseArchive())
{
$oBrowseArchive = new JSPopupMenuItem('UI:ArchiveModeOn', Dict::S('UI:ArchiveModeOn'), 'return ArchiveMode(true);');
$aActions[$oBrowseArchive->GetUID()] = $oBrowseArchive->GetMenuItem();
}
if (utils::CanLogOff())
{
$oLogOff = new URLPopupMenuItem('UI:LogOffMenu', Dict::S('UI:LogOffMenu'), utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff');
@@ -982,26 +1066,34 @@ EOF
$sRestrictions = Dict::S('UI:AccessRO-Users');
}
$sApplicationBanner = '';
if (strlen($sRestrictions) > 0)
{
$sIcon =
<<<EOF
<span class="fa-stack fa-sm">
<i class="fa fa-pencil fa-flip-horizontal fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span>
EOF;
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
$sApplicationBanner .= '<div id="admin-banner">';
$sApplicationBanner .= '<img src="../images/locked.png" style="vertical-align:middle;">';
$sApplicationBanner .= '&nbsp;<b>'.$sRestrictions.'</b>';
if (strlen($sAdminMessage) > 0)
{
$sApplicationBanner .= '&nbsp;<b>'.$sAdminMessage.'</b>';
$sRestrictions .= '&nbsp;'.$sAdminMessage;
}
$sApplicationBanner .= '</div>';
$this->AddApplicationMessage($sRestrictions, $sIcon);
}
if(strlen($this->m_sMessage))
$sApplicationMessages = '';
foreach ($this->m_aMessages as $aMessage)
{
$sApplicationBanner .= '<div id="admin-banner"><span style="padding:5px;">'.$this->m_sMessage.'<span></div>';
$sHtmlIcon = $aMessage['icon'] ? $aMessage['icon'] : '';
$sHtmlMessage = $aMessage['message'];
$sTitleAttr = $aMessage['tip'] ? 'title="'.htmlentities($aMessage['tip'], ENT_QUOTES, 'UTF-8').'"' : '';
$sApplicationMessages .= '<div class="app-message" '.$sTitleAttr.'><span class="app-message-icon">'.$sHtmlIcon.'</span><span class="app-message-body">'.$sHtmlMessage.'</div></span>';
}
$sApplicationBanner .= $sBannerExtraHtml;
$sApplicationBanner = "<div class=\"app-banner ui-helper-clearfix\">$sApplicationMessages$sBannerExtraHtml</div>";
if (!empty($sNorthPane))
{
@@ -1071,9 +1163,12 @@ EOF
$sHtml .= ' <div id="itop-breadcrumb"></div>';
$sHtml .= ' </td>';
$sHtml .= ' <td id="top-bar-table-search">';
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><table><tr><td></td><td><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"></div></div></td>';
$sHtml .= ' <td><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
$sHtml .= ' <td>'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">';
$sHtml .= ' <table id="top-left-buttons-area"><tr>';
$sHtml .= ' <td id="top-left-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"><input type="hidden" name="operation" value="full_text"/></div></div></td>';
$sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
$sHtml .= ' <td id="top-left-logoff-cell">'.self::FilterXSS($sLogOffMenu).'</td>';
$sHtml .= ' </tr></table></form></div>';
$sHtml .= ' </td>';
$sHtml .= ' </tr>';
$sHtml .= ' </table>';
@@ -1301,10 +1396,26 @@ EOF
}
/**
* Set the message to be displayed in the 'admin-banner' section at the top of the page
* Set the message to be displayed in the 'app-banner' section at the top of the page
*/
public function SetMessage($sMessage)
public function SetMessage($sHtmlMessage)
{
$this->m_sMessage = $sMessage;
$sHtmlIcon = '<span class="fa fa-comment fa-1x"></span>';
$this->AddApplicationMessage($sHtmlMessage, $sHtmlIcon);
}
/**
* Add message to be displayed in the 'app-banner' section at the top of the page
*/
public function AddApplicationMessage($sHtmlMessage, $sHtmlIcon = null, $sTip = null)
{
if (strlen($sHtmlMessage))
{
$this->m_aMessages[] = array(
'icon' => $sHtmlIcon,
'message' => $sHtmlMessage,
'tip' => $sTip
);
}
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class LoginWebPage
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -56,8 +56,13 @@ class LoginWebPage extends NiceWebPage
protected static $m_sLoginFailedMessage = '';
public function __construct($sTitle = 'iTop Login')
public function __construct($sTitle = null)
{
if($sTitle === null)
{
$sTitle = Dict::S('UI:Login:Title');
}
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->add_header("Cache-control: no-cache");
@@ -90,7 +95,7 @@ class LoginWebPage extends NiceWebPage
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
}
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?itopversion='.ITOP_VERSION;
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
@@ -112,7 +117,7 @@ class LoginWebPage extends NiceWebPage
case 'basic':
case 'url':
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
$this->add_header('HTTP/1.0 401 Unauthorized');
$this->add_header('Content-type: text/html; charset=iso-8859-1');
// Note: displayed when the user will click on Cancel
@@ -430,6 +435,8 @@ EOF
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_mode']);
unset($_SESSION['archive_mode']);
unset($_SESSION['impersonate_user']);
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
@@ -572,96 +579,95 @@ EOF
break;
}
$index++;
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
if ($sLoginMode == '')
}
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
if ($sLoginMode == '')
{
// First connection
$sDesiredLoginMode = utils::ReadParam('login_mode');
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
{
// First connection
$sDesiredLoginMode = utils::ReadParam('login_mode');
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
$sLoginMode = $sDesiredLoginMode;
}
else
{
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
}
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
{
// X-Combodo-Ajax is a special header automatically added to all ajax requests
// Let's reply that we're currently logged-out
header('HTTP/1.0 401 Unauthorized');
exit;
}
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
{
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
header('HTTP/1.0 401 Unauthorized');
header('Content-type: text/html; charset=iso-8859-1');
exit;
}
else if($iOnExit == self::EXIT_RETURN)
{
if (($sAuthUser !== '') && ($sAuthPwd === null))
{
$sLoginMode = $sDesiredLoginMode;
return self::EXIT_CODE_MISSINGPASSWORD;
}
else
{
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
}
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
{
// X-Combodo-Ajax is a special header automatically added to all ajax requests
// Let's reply that we're currently logged-out
header('HTTP/1.0 401 Unauthorized');
exit;
return self::EXIT_CODE_MISSINGLOGIN;
}
}
else
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
$oPage->output();
exit;
}
}
else
{
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
{
//echo "Check Credentials returned false for user $sAuthUser!";
self::ResetSession();
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
{
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
header('HTTP/1.0 401 Unauthorized');
header('Content-type: text/html; charset=iso-8859-1');
exit;
}
else if($iOnExit == self::EXIT_RETURN)
{
if (($sAuthUser !== '') && ($sAuthPwd === null))
{
return self::EXIT_CODE_MISSINGPASSWORD;
}
else
{
return self::EXIT_CODE_MISSINGLOGIN;
}
return self::EXIT_CODE_WRONGCREDENTIALS;
}
else
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
$oPage->output();
exit;
}
}
else
{
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
// User is Ok, let's save it in the session and proceed with normal login
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
if (MetaModel::GetConfig()->Get('log_usage'))
{
//echo "Check Credentials returned false for user $sAuthUser!";
self::ResetSession();
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
{
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
header('HTTP/1.0 401 Unauthorized');
header('Content-type: text/html; charset=iso-8859-1');
exit;
}
else if($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_WRONGCREDENTIALS;
}
else
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
$oPage->output();
exit;
}
}
else
{
// User is Ok, let's save it in the session and proceed with normal login
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
if (MetaModel::GetConfig()->Get('log_usage'))
{
$oLog = new EventLoginUsage();
$oLog->Set('userinfo', UserRights::GetUser());
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
$oLog->Set('message', 'Successful login');
$oLog->DBInsertNoReload();
}
$_SESSION['auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = $sLoginMode;
UserRights::_InitSessionCache();
$oLog = new EventLoginUsage();
$oLog->Set('userinfo', UserRights::GetUser());
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
$oLog->Set('message', 'Successful login');
$oLog->DBInsertNoReload();
}
$_SESSION['auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = $sLoginMode;
UserRights::_InitSessionCache();
}
}
return self::EXIT_CODE_OK;

View File

@@ -148,6 +148,7 @@ class ApplicationMenu
// the menu already exists, let's combine the conditions that make it visible
self::$aMenusIndex[$index]['node']->AddCondition($oMenuNode);
}
return $index;
}
@@ -174,7 +175,7 @@ class ApplicationMenu
{
$oMenuNode = self::GetMenuNode($aMenu['index']);
if (!$oMenuNode->IsEnabled()) continue; // Don't display a non-enabled menu
$oPage->AddToMenu('<h3>'.$oMenuNode->GetTitle().'</h3>');
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'">'.$oMenuNode->GetTitle().'</h3>');
$oPage->AddToMenu('<div>');
$aChildren = self::GetChildren($aMenu['index']);
if (count($aChildren) > 0)
@@ -217,11 +218,11 @@ EOF
$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
if ($sHyperlink != '')
{
$oPage->AddToMenu('<li'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'"'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
}
else
{
$oPage->AddToMenu('<li'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'"'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
}
$aCurrentMenu = self::$aMenusIndex[$index];
if ($iActiveMenu == $index)
@@ -320,6 +321,21 @@ EOF
}
return $sDefaultMenuId;
}
static public function GetRootMenuId($sMenuId)
{
$iMenuIndex = self::GetMenuIndexById($sMenuId);
if ($iMenuIndex == -1)
{
return '';
}
$oMenu = ApplicationMenu::GetMenuNode($iMenuIndex);
while ($oMenu->GetParentIndex() != -1)
{
$oMenu = ApplicationMenu::GetMenuNode($oMenu->GetParentIndex());
}
return $oMenu->GetMenuId();
}
}
/**
@@ -418,7 +434,12 @@ abstract class MenuNode
{
return $this->sMenuId;
}
public function GetParentIndex()
{
return $this->iParentIndex;
}
public function GetTitle()
{
return Dict::S("Menu:$this->sMenuId", str_replace('_', ' ', $this->sMenuId));

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Class PortalWebPage
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -96,15 +96,88 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
$oTimeFormat = new DateTimeFormat($sTimeFormat);
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
// Date picker options
$aPickerOptions = array(
'showOn' => 'button',
'buttonImage' => '../images/calendar.png',
'buttonImageOnly' => true,
'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(),
'constrainInput' => false,
'changeMonth' => true,
'changeYear' => true,
'dayNamesMin' => $aDaysMin,
'monthNamesShort' => $aMonthsShort,
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
);
$sJSDatePickerOptions = json_encode($aPickerOptions);
// Time picker additional options
$aPickerOptions['showOn'] = '';
$aPickerOptions['buttonImage'] = null;
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
$aPickerOptions['controlType'] = 'select';
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
if ($sJSLangShort != '"en"')
{
// More options that cannot be passed via json_encode since they must be evaluated client-side
$aMoreJSOptions = ",
'timeText': $.timepicker.regional[$sJSLangShort].timeText,
'hourText': $.timepicker.regional[$sJSLangShort].hourText,
'minuteText': $.timepicker.regional[$sJSLangShort].minuteText,
'secondText': $.timepicker.regional[$sJSLangShort].secondText,
'currentText': $.timepicker.regional[$sJSLangShort].currentText
}";
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
}
$this->add_script(
<<< EOF
function PrepareWidgets()
{
// note: each action implemented here must be idempotent,
// because this helper function might be called several times on a given page
$(".date-pick").datepicker($sJSDatePickerOptions);
// Hack for the date and time picker addon issue on Chrome (see #1305)
// The workaround is to instantiate the widget on demand
// It relies on the same markup, thus reverting to the original implementation should be straightforward
$(".datetime-pick:not(.is-widget-ready)").each(function(){
var oInput = this;
$(oInput).addClass('is-widget-ready');
$('<img class="datetime-pick-button" src="../images/calendar.png">')
.insertAfter($(this))
.on('click', function(){
$(oInput)
.datetimepicker($sJSDateTimePickerOptions)
.datetimepicker('show')
.datetimepicker('option', 'onClose', function(dateText,inst){
$(oInput).datetimepicker('destroy');
})
.on('click keypress', function(){
$(oInput).datetimepicker('hide');
});
});
});
}
EOF
);
$this->add_ready_script(
<<<EOF
@@ -146,34 +219,10 @@ try
}
});
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
PrepareWidgets();
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry_html').toggle(); });
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
$(document).ajaxSend(function(event, jqxhr, options) {
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
@@ -338,7 +387,7 @@ EOF
{
$sReadOnly = Dict::S('UI:AccessRO-Users');
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
$sApplicationBanner .= '<div id="admin-banner">';
$sApplicationBanner .= '<div class="app-message">';
$sApplicationBanner .= '<img src="../images/locked.png" style="vertical-align:middle;">';
$sApplicationBanner .= '&nbsp;<b>'.$sReadOnly.'</b>';
if (strlen($sAdminMessage) > 0)
@@ -767,7 +816,7 @@ EOF
$sClass = get_class($oObj);
$sStimulus = trim(utils::ReadPostedParam('apply_stimulus', ''));
$sTargetState = '';
$aExpectedAttributes = array();
if (!empty($sStimulus))
{
// Compute the target state
@@ -777,10 +826,10 @@ EOF
{
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel()));
}
$sTargetState = $aTransitions[$sStimulus]['target_state'];
}
$aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/);
}
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $sTargetState);
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $aExpectedAttributes);
// Optional: apply a stimulus
//

View File

@@ -1,531 +0,0 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* SqlBlock - display tables or charts, given an SQL query - use cautiously!
*
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
/**
* Helper class to design optimized dashboards, based on an SQL query
*
*/
class SqlBlock
{
protected $m_sQuery;
protected $m_aColumns;
protected $m_sTitle;
protected $m_sType;
protected $m_aParams;
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
{
$this->m_sQuery = $sQuery;
$this->m_aColumns = $aColumns;
$this->m_sTitle = $sTitle;
$this->m_sType = $sType;
$this->m_aParams = $aParams;
}
/**
* Constructs a SqlBlock object from an XML template
/*
*
* <sqlblock>
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) &lt; start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
* <type>table</type>
* <title>UserRequest:Overview-Title</title>
* <parameter>
* <name>param1</name>
* <type>context</type>
* <mapping>org_id</mapping>
* </parameter>
* <column>
* <name>Date</name>
* <label>UserRequest:Overview-Date</label>
* <drilldown></drilldown>
* </column>
* <column>
* <name>Count</name>
* <label>UserRequest:Overview-Count</label>
* <drilldown>SELECT UserIssue WHERE date_format(start_date, '%d') = :Date</drilldown>
* </column>
* </sqlblock>
*
* Tags
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. &lt; for <)
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
* - title: optional title, typed in clear or given as a dictionnary entry
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
* either the string "1" if there is no restriction on the organisation in iTop
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
* - column: specification of a column (not displayed if omitted)
* - column / name: name of the column in the SQL query (use aliases)
* - column / label: label, typed in clear or given as a dictionnary entry
* - column / drilldown: NOT IMPLEMENTED YET - OQL with parameters corresponding to column names (in the query)
*
* @param $sTemplate string The XML template
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
*/
public static function FromTemplate($sTemplate)
{
$oXml = simplexml_load_string('<root>'.$sTemplate.'</root>', 'SimpleXMLElement', LIBXML_NOCDATA);
if (false)
{
// Debug
echo "<pre>\n";
print_r($oXml);
echo "</pre>\n";
}
if (isset($oXml->title))
{
$sTitle = (string)$oXml->title;
}
if (isset($oXml->type))
{
$sType = (string)$oXml->type;
}
else
{
$sType = 'table';
}
if (!isset($oXml->sql))
{
throw new Exception('Missing tag "sql" in sqlblock');
}
$sQuery = (string)$oXml->sql;
$aColumns = array();
if (isset($oXml->column))
{
foreach ($oXml->column AS $oColumnData)
{
if (!isset($oColumnData->name))
{
throw new Exception("Missing tag 'name' in sqlblock/column");
}
$sName = (string) $oColumnData->name;
if (strlen($sName) == 0)
{
throw new Exception("Empty tag 'name' in sqlblock/column");
}
$aColumns[$sName] = array();
if (isset($oColumnData->label))
{
$sLabel = (string)$oColumnData->label;
if (strlen($sLabel) > 0)
{
$aColumns[$sName]['label'] = Dict::S($sLabel);
}
}
if (isset($oColumnData->drilldown))
{
$sDrillDown = (string)$oColumnData->drilldown;
if (strlen($sDrillDown) > 0)
{
$aColumns[$sName]['drilldown'] = $sDrillDown;
}
}
}
}
$aParams = array();
if (isset($oXml->parameter))
{
foreach ($oXml->parameter AS $oParamData)
{
if (!isset($oParamData->name))
{
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
}
$sName = (string) $oParamData->name;
if (strlen($sName) == 0)
{
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
}
if (!isset($oParamData->mapping))
{
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
}
$sMapping = (string) $oParamData->mapping;
if (strlen($sMapping) == 0)
{
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
}
if (isset($oParamData->type))
{
$sParamType = $oParamData->type;
}
else
{
$sParamType = 'context';
}
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
}
}
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
}
/**
* Applies the defined parameters into the SQL query
* @return string the SQL query to execute
*/
public function BuildQuery()
{
$oAppContext = new ApplicationContext();
$sQuery = $this->m_sQuery;
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
foreach($this->m_aParams as $sName => $aParam)
{
if ($aParam['type'] == 'context')
{
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
if (empty($value))
{
$sSQLExpr = '(1)';
}
else
{
// Special case for managing the hierarchy of organizations
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode != false)
{
// organizations are in hierarchy... gather all the orgs below the given one...
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
$aOrgIds = array();
while($oOrg = $oSet->Fetch())
{
$aOrgIds[]= $oOrg->GetKey();
}
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
}
}
return $sQuery;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
if (empty($aExtraParams['currentId']))
{
$sId = 'sqlblock_'.$oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
}
else
{
$sId = $aExtraParams['currentId'];
}
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
$sQuery = $this->BuildQuery();
$res = CMDBSource::Query($sQuery);
$aQueryCols = CMDBSource::GetColumns($res);
// Prepare column definitions (check + give default values)
//
foreach($this->m_aColumns as $sName => $aColumnData)
{
if (!in_array($sName, $aQueryCols))
{
throw new Exception("Unknown column name '$sName' in sqlblock column");
}
if (!isset($aColumnData['label']))
{
$this->m_aColumns[$sName]['label'] = $sName;
}
if (isset($aColumnData['drilldown']) && !empty($aColumnData['drilldown']))
{
// Check if the OQL is valid
try
{
$this->m_aColumns[$sName]['filter'] = DBObjectSearch::FromOQL($aColumnData['drilldown']);
}
catch(OQLException $e)
{
unset($aColumnData['drilldown']);
}
}
}
if (strlen($this->m_sTitle) > 0)
{
$oPage->add("<h2>".Dict::S($this->m_sTitle)."</h2>\n");
}
switch ($this->m_sType)
{
case 'bars':
case 'pie':
$aColNames = array_keys($this->m_aColumns);
$sXColName = $aColNames[0];
$sYColName = $aColNames[1];
$aData = array();
$aRows = array();
while($aRow = CMDBSource::FetchArray($res))
{
$aData[$aRow[$sXColName]] = $aRow[$sYColName];
$aRows[$aRow[$sXColName]] = $aRow;
}
$this->RenderChart($oPage, $sId, $aData, $this->m_aColumns[$sYColName]['drilldown'], $aRows);
break;
default:
case 'table':
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (!empty($sContext))
{
$sContext = '&'.$sContext;
}
$aDisplayConfig = array();
foreach($this->m_aColumns as $sName => $aColumnData)
{
$aDisplayConfig[$sName] = array('label' => $aColumnData['label'], 'description' => '');
}
$aDisplayData = array();
while($aRow = CMDBSource::FetchArray($res))
{
$aSQLColNames = array_keys($aRow);
$aDisplayRow = array();
foreach($this->m_aColumns as $sName => $aColumnData)
{
if (isset($aColumnData['filter']))
{
$sFilter = $aColumnData['drilldown'];
$sClass = $aColumnData['filter']->GetClass();
$sFilter = str_replace('SELECT '.$sClass, '', $sFilter);
foreach($aSQLColNames as $sColName)
{
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRow[$sColName] )."'", $sFilter);
}
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&oql_clause='.urlencode($sFilter).'&format=html'.$sContext;
$aDisplayRow[$sName] = '<a href="'.$sURL.'">'.$aRow[$sName]."</a>";
}
else
{
$aDisplayRow[$sName] = $aRow[$sName];
}
}
$aDisplayData[] = $aDisplayRow;
}
$oPage->table($aDisplayConfig, $aDisplayData);
break;
}
}
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{
$sHtml = '';
return $sHtml;
}
protected function RenderChart($oPage, $sId, $aValues, $sDrillDown = '', $aRows = array())
{
// 1- Compute Open Flash Chart data
//
$aValueKeys = array();
$index = 0;
if ((count($aValues) > 0) && ($sDrillDown != ''))
{
$oFilter = DBObjectSearch::FromOQL($sDrillDown);
$sClass = $oFilter->GetClass();
$sOQLClause = str_replace('SELECT '.$sClass, '', $sDrillDown);
$aSQLColNames = array_keys(current($aRows)); // Read the list of columns from the current (i.e. first) element of the array
$oAppContext = new ApplicationContext();
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&format=html&'.$oAppContext->GetForLink().'&oql_clause=';
}
$aURLs = array();
foreach($aValues as $key => $value)
{
// Make sure that values are integers (so that max() will work....)
// and build an array of STRING with the keys (numeric keys are transformed into string by PHP :-(
$aValues[$key] = (int)$value;
$aValueKeys[] = (string)$key;
// Build the custom query for the 'drill down' on each element
if ($sDrillDown != '')
{
$sFilter = $sOQLClause;
foreach($aSQLColNames as $sColName)
{
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRows[$key][$sColName] )."'", $sFilter);
$aURLs[$index] = $sURL.urlencode($sFilter);
}
}
$index++;
}
$oChart = new open_flash_chart();
if ($this->m_sType == 'bars')
{
$oChartElement = new bar_glass();
if (count($aValues) > 0)
{
$maxValue = max($aValues);
}
else
{
$maxValue = 1;
}
$oYAxis = new y_axis();
$aMagicValues = array(1,2,5,10);
$iMultiplier = 1;
$index = 0;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
while($maxValue > $iTop)
{
$index++;
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
if (($index % count($aMagicValues)) == 0)
{
$iMultiplier = $iMultiplier * 10;
}
}
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
$oYAxis->set_range(0, $iTop, $iMultiplier);
$oChart->set_y_axis( $oYAxis );
$aBarValues = array();
foreach($aValues as $iValue)
{
$oBarValue = new bar_value($iValue);
$oBarValue->on_click("ofc_drilldown_{$sId}");
$aBarValues[] = $oBarValue;
}
$oChartElement->set_values($aBarValues);
//$oChartElement->set_values(array_values($aValues));
$oXAxis = new x_axis();
$oXLabels = new x_axis_labels();
// set them vertical
$oXLabels->set_vertical();
// set the label text
$oXLabels->set_labels($aValueKeys);
// Add the X Axis Labels to the X Axis
$oXAxis->set_labels( $oXLabels );
$oChart->set_x_axis( $oXAxis );
}
else
{
$oChartElement = new pie();
$oChartElement->set_start_angle( 35 );
$oChartElement->set_animate( true );
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
$aData = array();
foreach($aValues as $sValue => $iValue)
{
$oPieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
$oPieValue->on_click("ofc_drilldown_{$sId}");
$aData[] = $oPieValue;
}
$oChartElement->set_values( $aData );
$oChart->x_axis = null;
}
// Title given in HTML
//$oTitle = new title($this->m_sTitle);
//$oChart->set_title($oTitle);
$oChart->set_bg_colour('#FFFFFF');
$oChart->add_element( $oChartElement );
$sData = $oChart->toPrettyString();
$sData = json_encode($sData);
// 2- Declare the Javascript function that will render the chart data\
//
$oPage->add_script(
<<< EOF
function ofc_get_data_{$sId}()
{
return $sData;
}
EOF
);
if (count($aURLs) > 0)
{
$sURLList = '';
foreach($aURLs as $index => $sURL)
{
$sURLList .= "\taURLs[$index] = '".addslashes($sURL)."';\n";
}
$oPage->add_script(
<<< EOF
function ofc_drilldown_{$sId}(index)
{
var aURLs = new Array();
{$sURLList}
var sURL = aURLs[index];
window.location.href = sURL; // Navigate !
}
EOF
);
}
// 3- Insert the Open Flash chart
//
$oPage->add("<div id=\"$sId\"><div>\n");
$oPage->add_ready_script(
<<<EOF
swfobject.embedSWF( "../images/open-flash-chart.swf",
"{$sId}",
"100%", "300","9.0.0",
"expressInstall.swf",
{"get-data":"ofc_get_data_{$sId}", "id":"{$sId}"},
{'wmode': 'transparent'}
);
EOF
);
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class DisplayTemplate
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -353,11 +353,15 @@ class ObjectDetailsTemplate extends DisplayTemplate
if ($iFlags & OPT_ATT_SLAVE)
{
$iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
$sSynchroIcon = "&nbsp;<img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
$sTip = '';
foreach($aReasons as $aRow)
{
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
$sTip .= "<div class='synchro-source'>";
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
}
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -54,7 +54,7 @@
* | | +--------+ +-----+ | |
* | +--------------------------------------------+ |
* +------------------------------------------------+
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -101,7 +101,7 @@ class UIExtKeyWidget
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
{
if (!is_null($bSearchMode))
{
@@ -111,12 +111,12 @@ class UIExtKeyWidget
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if($this->bSearchMode)
{
@@ -141,16 +141,22 @@ class UIExtKeyWidget
{
throw new Exception('Implementation: null value for allowed values definition');
}
elseif ($oAllowedValues->Count() < $iMaxComboLength)
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
if ($oAllowedValues->Count($iMaxComboLength * 2) < $iMaxComboLength)
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
switch($sDisplayStyle)
{
case 'radio':
case 'radio_horizontal':
case 'radio_vertical':
$sValidationField = "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
$sHTMLValue = '';
$sValidationField = null;
$bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false;
$oAllowedValues->Rewind();
@@ -159,7 +165,7 @@ class UIExtKeyWidget
{
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
}
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", $bMandatory, $bVertical, $sValidationField);
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
$aEventsList[] ='change';
break;
@@ -169,25 +175,27 @@ class UIExtKeyWidget
$sSelectMode = 'true';
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
if ($this->bSearchMode)
{
if ($bSearchMultiple)
{
$sHTMLValue = "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
$sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
}
else
{
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
}
}
else
{
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
}
$oAllowedValues->Rewind();
while($oObj = $oAllowedValues->Fetch())
{
@@ -206,6 +214,8 @@ class UIExtKeyWidget
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>\n";
$sHTMLValue .= "</div>\n";
if (($this->bSearchMode) && $bSearchMultiple)
{
$aOptions = array(
@@ -221,7 +231,7 @@ class UIExtKeyWidget
}
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
@@ -256,8 +266,8 @@ EOF
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
// the input for the auto-complete
$sHTMLValue = "<input count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/>";
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
@@ -266,7 +276,7 @@ EOF
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
@@ -281,7 +291,7 @@ EOF
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/>&nbsp;";
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
$oPage->add_ready_script(
<<<EOF
if ($('#ac_tree_{$this->iId}').length == 0)
@@ -293,7 +303,9 @@ EOF
}
if ($bCreate && $bExtensions)
{
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/>&nbsp;";
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
$oPage->add_ready_script(
<<<EOF
if ($('#ajax_{$this->iId}').length == 0)
@@ -303,11 +315,14 @@ EOF
EOF
);
}
if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
{
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
}
$sHTMLValue .= "</span>"; // end of no wrap
$sHTMLValue .= "</div>";
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
//{
$sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
//}
return $sHTMLValue;
}
@@ -328,7 +343,7 @@ EOF
$aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->sTargetClass);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
@@ -372,7 +387,11 @@ EOF
$oFilter->ChangeClass($sRemoteClass);
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
$iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
}
@@ -389,15 +408,40 @@ EOF
{
throw new Exception('Implementation: null value for allowed values definition');
}
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
$iMax = 150;
$oValuesSet->SetLimit($iMax);
$oValuesSet->SetSort(false);
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
foreach($aValues as $sKey => $sFriendlyName)
$aValuesEquals = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals_start_with');
//asort($aValuesEquals);
foreach($aValuesEquals as $sKey => $sFriendlyName)
{
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
$iMax--;
}
if ($iMax <= 0)
{
return;
}
$oValuesSet->SetLimit($iMax);
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
asort($aValuesContains);
foreach($aValuesContains as $sKey => $sFriendlyName)
{
if (!isset($aValuesEquals[$sKey]))
{
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
}
}
}
/**
* Get the display name of the selected object, to fill back the autocomplete
*/
@@ -416,7 +460,49 @@ EOF
return '';
}
}
/**
* Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
*
* @param WebPage $oPage
*/
public function GetClassSelectionForm(WebPage $oPage)
{
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
$aPossibleClasses = array();
foreach($aSubClasses as $sCandidateClass)
{
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
}
}
$sDialogTitle = '';
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add('<form>');
$sClassLabel = MetaModel::GetName($this->sTargetClass);
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
$oPage->add('<nobr><select name="class">');
asort($aPossibleClasses);
foreach($aPossibleClasses as $sClassName => $sClassLabel)
{
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
}
$oPage->add('</select>');
$oPage->add('&nbsp; <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
$oPage->add('</form>');
$oPage->add('</div></div></div>');
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
}
/**
* Get the form to create a new object of the 'target' class
*/
@@ -484,9 +570,9 @@ EOF
}
try
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj));
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
}
catch(MissingQueryArgument $e)
{
@@ -497,6 +583,7 @@ EOF
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter);
}
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
$this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
@@ -600,4 +687,3 @@ EOF
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,9 +20,8 @@
* Class UIHTMLEditorWidget
* UI wdiget for displaying and editing one-way encrypted passwords
*
* @author Phil Eddies
* @author Romain Quetiez
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -65,7 +64,7 @@ class UIHTMLEditorWidget
$sHelpText = $this->m_sHelpText;
$sValidationField = $this->m_sValidationField;
$sHtmlValue = "<table><tr><td><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></td><td>$sValidationField</td></tr></table>";
$sHtmlValue = "<div class=\"field_input_zone field_input_html\"><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></div>$sValidationField";
// Replace the text area with CKEditor
// To change the default settings of the editor,
@@ -99,9 +98,26 @@ class UIHTMLEditorWidget
// Could also be bound to 'instanceReady.ckeditor'
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
$oPage->add_ready_script(
<<<EOF
$('#$iId').bind('update', function(evt){
BlockField('cke_$iId', $('#$iId').attr('disabled'));
//Delayed execution - ckeditor must be properly initialized before setting readonly
var retryCount = 0;
var oMe = $('#$iId');
var delayedSetReadOnly = function () {
if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) {
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
}
else
{
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
}
};
setTimeout(delayedSetReadOnly, 50);
});
EOF
);
return $sHtmlValue;
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Class UILinksWidgetDirect
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -30,7 +30,14 @@ class UILinksWidgetDirect
protected $sInputid;
protected $sNameSuffix;
protected $sLinkedClass;
/**
* UILinksWidgetDirect constructor.
* @param string $sClass
* @param string $sAttCode
* @param string $sInputId
* @param string $sNameSuffix
*/
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '')
{
$this->sClass = $sClass;
@@ -71,8 +78,15 @@ class UILinksWidgetDirect
}
}
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
/**
* @param WebPage $oPage
* @param DBObjectSet|ormLinkSet $oValue
* @param array $aArgs
* @param string $sFormPrefix
* @param DBObject $oCurrentObj
*/
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
switch($oLinksetDef->GetEditMode())
@@ -115,8 +129,16 @@ class UILinksWidgetDirect
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
}
}
protected function DisplayAsBlock(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
/**
* @param WebPage $oPage
* @param DBObjectSet $oValue
* @param array $aArgs
* @param string $sFormPrefix
* @param DBObject $oCurrentObj
* @param bool $bDisplayMenu
*/
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
{
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sTargetClass = $oLinksetDef->GetLinkedClass();
@@ -143,6 +165,7 @@ class UILinksWidgetDirect
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
'menu' => $bDisplayMenu,
'menu_actions_target' => '_blank',
'default' => $aDefaults,
'table_id' => $this->sClass.'_'.$this->sAttCode,
);
@@ -151,45 +174,11 @@ class UILinksWidgetDirect
$oBlock->Display($oPage, $this->sInputid, $aParams);
}
}
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
{
$aAttribs = $this->GetTableConfig();
$oValue->Rewind();
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
$aData = array();
while($oLinkObj = $oValue->Fetch())
{
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
$aData[] = $aRow;
}
$oPage->table($aAttribs, $aData);
$oPage->add('</td></tr></table>'); //listcontainer
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
$aLabels = array(
'delete' => Dict::S('UI:Button:Delete'),
// 'modify' => 'Modify...' ,
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
'remove' => Dict::S('UI:Button:Remove'),
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
);
$oContext = new ApplicationContext();
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
$sJSONLabels = json_encode($aLabels);
$sJSONButtons = json_encode($aButtons);
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });");
}
/**
* @param WebPage $oPage
* @param string $sProposedRealClass
*/
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
{
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
@@ -215,14 +204,14 @@ class UILinksWidgetDirect
$aKeys = array_keys($aPossibleClasses);
$sRealClass = $aKeys[0];
}
if ($sRealClass != '')
{
$oPage->add("<h1>".MetaModel::GetClassIcon($sRealClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($sRealClass))."</h1>\n");
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aFieldFlags = array( $sExtKeyToMe => OPT_ATT_HIDDEN);
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
}
else
{
@@ -239,7 +228,61 @@ class UILinksWidgetDirect
}
$oPage->add('</div></div>');
}
/**
* @param WebPage $oPage
* @param DBObjectSet $oValue
* @param array $aArgs
* @param string $sFormPrefix
* @param DBObject $oCurrentObj
* @param array $aButtons
*/
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
{
$aAttribs = $this->GetTableConfig();
$oValue->Rewind();
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
$aData = array();
while($oLinkObj = $oValue->Fetch())
{
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
$aData[] = $aRow;
}
$oPage->table($aAttribs, $aData);
$oPage->add('</td></tr></table>'); //listcontainer
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
$aLabels = array(
'delete' => Dict::S('UI:Button:Delete'),
// 'modify' => 'Modify...' ,
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
'remove' => Dict::S('UI:Button:Remove'),
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
);
$oContext = new ApplicationContext();
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
$sJSONLabels = json_encode($aLabels);
$sJSONButtons = json_encode($aButtons);
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
}
/**
* @param WebPage $oPage
* @param DBObject $oCurrentObj
* @throws Exception
*/
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
{
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
@@ -262,7 +305,7 @@ class UILinksWidgetDirect
{
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
}
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->sLinkedClass);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
@@ -274,16 +317,15 @@ class UILinksWidgetDirect
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$oPage->add($sHtml);
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->sInputId}.SearchObjectsToAdd);");
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix}').resize(oWidget{$this->siInputId}.UpdateSizes);");
}
/**
* Search for objects to be linked to the current object (i.e "remote" objects)
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of $this->sLinkedClass
* @param array $aAlreadyLinked Array of indentifiers of objects which are already linke to the current object (or about to be linked)
* @param DBObject $oCurrentObj The object currently being edited... if known...
* @throws Exception
*/
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null)
{
@@ -328,6 +370,10 @@ class UILinksWidgetDirect
$oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", array('menu' => false, 'cssCount'=> '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid)); // Don't display the 'Actions' menu on the results
}
/**
* @param WebPage $oP
* @param $oFullSetFilter
*/
public function DoAddObjects(WebPage $oP, $oFullSetFilter)
{
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
@@ -355,7 +401,14 @@ class UILinksWidgetDirect
}
return $aAttribs;
}
/**
* @param WebPage $oPage
* @param string $sRealClass
* @param array $aValues
* @param int $iTempId
* @return mixed
*/
public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
{
if ($sRealClass == '')
@@ -367,7 +420,13 @@ class UILinksWidgetDirect
return $this->GetObjectRow($oPage, $oLinkObj, $iTempId);
}
/**
* @param WebPage $oPage
* @param $oLinkObj
* @param int $iTempId
* @return mixed
*/
protected function GetObjectRow($oPage, $oLinkObj, $iTempId)
{
$aAttribs = $this->GetTableConfig();
@@ -383,7 +442,7 @@ class UILinksWidgetDirect
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBSearch $oSearch
* @param DBSearch|DBObjectSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{
@@ -402,7 +461,6 @@ class UILinksWidgetDirect
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
$defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class UILinksWidget
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -39,7 +39,15 @@ class UILinksWidget
protected $m_sLinkedClass;
protected $m_sRemoteClass;
protected $m_bDuplicatesAllowed;
/**
* UILinksWidget constructor.
* @param string $sClass
* @param string $sAttCode
* @param int $iInputId
* @param string $sNameSuffix
* @param bool $bDuplicatesAllowed
*/
public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
{
$this->m_sClass = $sClass;
@@ -92,16 +100,19 @@ class UILinksWidget
}
}
}
/**
* A one-row form for editing a link record
* @param WebPage $oP Web page used for the ouput
* @param DBObject $oLinkedObj The object to which all the elements of the linked set refer to
* @param DBObject $oLinkedObj Remote object
* @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment of the one-row form
* @param array $aArgs Extra context arguments
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
* @param int $iUniqueId A unique identifier of new links
* @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable)
* @return array The HTML fragment of the one-row form
*/
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array(), $oCurrentObj )
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
{
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
$aRow = array();
@@ -115,16 +126,33 @@ class UILinksWidget
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$key}";
$aArgs['this'] = $linkObjOrId;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"$key\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
$sSafeId = utils::GetSafeId($sFieldId);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs);
$aFieldsMap[$sFieldCode] = $sSafeId;
}
if($bReadOnly)
{
$aRow['form::checkbox'] = "";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$aRow[$sFieldCode] = $sDisplayValue;
}
}
else
{
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$key\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
$sSafeId = utils::GetSafeId($sFieldId);
$sValue = $linkObjOrId->Get($sFieldCode);
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId, $sNameSuffix, 0, $aArgs).
'</div></div></div>';
$aFieldsMap[$sFieldCode] = $sSafeId;
}
}
$sState = $linkObjOrId->GetState();
}
else
@@ -135,78 +163,60 @@ class UILinksWidget
// New link existing only in memory
$oNewLinkObj = $linkObjOrId;
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$linkObjOrId = -$iRemoteObjKey;
}
else
{
$iRemoteObjKey = -$linkObjOrId;
$iRemoteObjKey = $linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
}
$sPrefix .= "[$linkObjOrId][";
$sPrefix .= "[-$iUniqueId][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
$aArgs['this'] = $oNewLinkObj;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"-$iUniqueId\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']';
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']';
$sSafeId = utils::GetSafeId($sFieldId);
$sValue = $oNewLinkObj->Get($sFieldCode);
$sDisplayValue = $oNewLinkObj->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $oNewLinkObj->Get($sFieldCode) /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId /* id */, $sNameSuffix, 0, $aArgs).
'</div></div></div>';
$aFieldsMap[$sFieldCode] = $sSafeId;
}
$sState = '';
$sJSDaysMin = json_encode(array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')));
$sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')));
$iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek');
$oP->add_script(
<<<EOF
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
// Rows created with ajax call need OnLinkAdded call.
// Rows added before loading the form cannot call OnLinkAdded.
if ($iUniqueId > 0)
{
$oP->add_script(
<<<EOF
PrepareWidgets();
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
EOF
);
);
}
}
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
if(!$bReadOnly)
{
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
}
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
@@ -227,7 +237,11 @@ EOF
/**
* Display one row of the whole form
* @return none
* @param WebPage $oP
* @param array $aConfig
* @param array $aRow
* @param int $iRowId
* @return string
*/
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
{
@@ -245,8 +259,8 @@ EOF
/**
* Display the table with the form for editing all the links at once
* @param WebPage $oP The web page used for the output
* @param Hash $aConfig The table's header configuration
* @param Hash $aData The tabular data to be displayed
* @param array $aConfig The table's header configuration
* @param array $aData The tabular data to be displayed
* @return string Html fragment representing the form table
*/
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
@@ -283,48 +297,60 @@ EOF
return $sHtml;
}
/**
* Get the HTML fragment corresponding to the linkset editing widget
* @param WebPage $oP The web page used for all the output
* @param DBObjectSet The initial value of the linked set
* @param Hash $aArgs Extra context arguments
* @param WebPage $oPage
* @param DBObject|ormLinkSet $oValue
* @param array $aArgs Extra context arguments
* @param string $sFormPrefix prefix of the fields in the current form
* @param DBObject $oCurrentObj the current object to which the linkset is related
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$sHtmlValue = '';
$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
$sHtmlValue .= "<div id=\"linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
$sHtmlValue .= "<input type=\"hidden\" id=\"{$sFormPrefix}{$this->m_iInputId}\">\n";
$oValue->Rewind();
$aForm = array();
$iAddedId = 1; // Unique id for new links
while($oCurrentLink = $oValue->Fetch())
{
$aRow = array();
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote));
if ($oCurrentLink->IsNew())
{
$key = -$oLinkedObj->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
}
else
{
$key = $oCurrentLink->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
}
// We try to retrieve the remote object as usual
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
// If successful, it means that we can edit its link
if($oLinkedObj !== null)
{
$bReadOnly = false;
}
// Else we retrieve it without restrictions (silos) and will display its link as readonly
else
{
$bReadOnly = true;
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true);
}
if ($oCurrentLink->IsNew())
{
$key = -($iAddedId++);
}
else
{
$key = $oCurrentLink->GetKey();
}
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
}
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->m_sRemoteClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script(<<<EOF
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}');
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
oWidget{$this->m_iInputId}.Init();
$('#{$this->m_iInputId}').bind('update_value', function() { $(this).val(oWidget{$this->m_iInputId}.GetUpdatedValue()); })
EOF
);
$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
@@ -332,13 +358,19 @@ EOF
$sHtmlValue .= "<span style=\"clear:both;\"><p>&nbsp;</p></span>\n";
$sHtmlValue .= "</div>\n";
$oPage->add_at_the_end("<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\"></div>"); // To prevent adding forms inside the main form
return $sHtmlValue;
return $sHtmlValue;
}
/**
* @param string $sClass
* @param string $sAttCode
* @return string
*/
protected static function GetTargetClass($sClass, $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sLinkedClass = $oAttDef->GetLinkedClass();
$sTargetClass = '';
switch(get_class($oAttDef))
{
case 'AttributeLinkedSetIndirect':
@@ -353,10 +385,14 @@ EOF
return $sTargetClass;
}
/**
* @param WebPage $oPage
* @param DBObject $oCurrentObj
*/
public function GetObjectPickerDialog($oPage, $oCurrentObj)
{
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->m_sRemoteClass);
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
@@ -397,51 +433,31 @@ EOF
}
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
{
// Positive IDs correspond to existing link records
// negative IDs correspond to "remote" objects to be linked
$aLinkIds = array();
$aRemoteObjIds = array();
foreach($aAlreadyLinkedIds as $iId)
{
if ($iId > 0)
{
$aLinkIds[] = $iId;
}
else
{
$aRemoteObjIds[] = -$iId;
}
}
if (count($aLinkIds) >0)
{
// Search for the links to find to which "remote" object they are linked
$oLinkFilter = new DBObjectSearch($this->m_sLinkedClass);
$oLinkFilter->AddCondition('id', $aLinkIds, 'IN');
$oLinkSet = new CMDBObjectSet($oLinkFilter);
while($oLink = $oLinkSet->Fetch())
{
$aRemoteObjIds[] = $oLink->Get($this->m_sExtKeyToRemote);
}
}
$oFilter->AddCondition('id', $aRemoteObjIds, 'NOTIN');
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
}
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $oFullSetFilter, $oCurrentObj)
/**
* @param WebPage $oP
* @param int $iMaxAddedId
* @param $oFullSetFilter
* @param DBObject $oCurrentObj
*/
public function DoAddObjects(WebPage $oP, $iMaxAddedId, $oFullSetFilter, $oCurrentObj)
{
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
$iAdditionId = $iMaxAddedId + 1;
foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
if (is_object($oLinkedObj))
{
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId, array(), $oCurrentObj ); // Not yet created link get negative Ids
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId));
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iAdditionId));
$iAdditionId++;
}
else
{
@@ -453,7 +469,7 @@ EOF
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBSearch $oSearch
* @param DBSearch|DBObjectSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{
@@ -472,7 +488,6 @@ EOF
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
$defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class
@@ -492,4 +507,3 @@ EOF
}
}
}
?>

View File

@@ -58,9 +58,15 @@ class UIPasswordWidget
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><span id="fstatus_'.$this->iId.'"></span><br/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
$sHtmlValue .= '</div>';
$sHtmlValue .= '<span class="form_validation" id="v_'.$this->iId.'"></span><span class="field_status" id="fstatus_'.$this->iId.'"></span>';
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate

View File

@@ -1,423 +0,0 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class UILinksWizard
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class UILinksWizard
{
protected $m_sClass;
protected $m_sLinkageAttr;
protected $m_iObjectId;
protected $m_sLinkedClass;
protected $m_sLinkingAttCode;
protected $m_aEditableFields;
protected $m_aTableConfig;
public function __construct($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass = '')
{
$this->m_sClass = $sClass;
$this->m_sLinkageAttr = $sLinkageAttr;
$this->m_iObjectId = $iObjectId;
$this->m_sLinkedClass = $sLinkedClass; // Will try to guess below, if it's empty
$this->m_sLinkingAttCode = ''; // Will be filled once we've found the attribute corresponding to the linked class
$this->m_aEditableFields = array();
$this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onChange=\"var value = this.checked; $('.selection').each( function() { this.checked = value; } );OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef->IsExternalKey() && ($sAttCode != $this->m_sLinkageAttr))
{
if (empty($this->m_sLinkedClass))
{
// This is a class of objects we can manage !
// Since nothing was specify, any class will do !
$this->m_sLinkedClass = $oAttDef->GetTargetClass();
$this->m_sLinkingAttCode = $sAttCode;
}
else if ($this->m_sLinkedClass == $oAttDef->GetTargetClass())
{
// This is the class of objects we want to manage !
$this->m_sLinkingAttCode = $sAttCode;
}
}
else if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField()))
{
$this->m_aEditableFields[] = $sAttCode;
$this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
}
}
if (empty($this->m_sLinkedClass))
{
throw( new Exception(Dict::Format('UI:Error:IncorrectLinkDefinition_LinkedClass_Class', $sLinkedClass, $sClass)));
}
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
}
}
public function Display(WebPage $oP, $aExtraParams = array())
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
$sTargetClass = $oAttDef->GetTargetClass();
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
$oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetRawName());
$oP->add("<div class=\"wizContainer\">\n");
$oP->add("<form method=\"post\">\n");
$oP->add("<div class=\"page_header\">\n");
$oP->add("<input type=\"hidden\" id=\"linksToRemove\" name=\"linksToRemove\" value=\"\">\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"do_modify_links\">\n");
$oP->add("<input type=\"hidden\" name=\"class\" value=\"{$this->m_sClass}\">\n");
$oP->add("<input type=\"hidden\" name=\"linkage\" value=\"{$this->m_sLinkageAttr}\">\n");
$oP->add("<input type=\"hidden\" name=\"object_id\" value=\"{$this->m_iObjectId}\">\n");
$oP->add("<input type=\"hidden\" name=\"linking_attcode\" value=\"{$this->m_sLinkingAttCode}\">\n");
$oP->add("<h1>".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
$oP->add("</div>\n");
$oP->add_script(
<<<EOF
function OnSelectChange()
{
var nbChecked = $('.selection:checked').length;
if (nbChecked > 0)
{
$('#btnRemove').removeAttr('disabled');
}
else
{
$('#btnRemove').attr('disabled','disabled');
}
}
function RemoveSelected()
{
$('.selection:checked').each(
function()
{
$('#linksToRemove').val($('#linksToRemove').val() + ' ' + this.value);
$('#row_'+this.value).remove();
}
);
// Disable the button since all the selected items have been removed
$('#btnRemove').attr('disabled','disabled');
// Re-run the zebra plugin to properly highlight the remaining lines
$('.listResults').trigger('update');
}
function AddObjects()
{
// TO DO: compute the list of objects already linked with the current Object
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { 'operation': 'addObjects',
'class': '{$this->m_sClass}',
'linkageAttr': '{$this->m_sLinkageAttr}',
'linkedClass': '{$this->m_sLinkedClass}',
'objectId': '{$this->m_iObjectId}'
},
function(data)
{
$('#ModalDlg').html(data);
dlgWidth = $(document).width() - 100;
$('#ModalDlg').css('width', dlgWidth);
$('#ModalDlg').css('left', 50);
$('#ModalDlg').css('top', 50);
$('#ModalDlg').dialog( 'open' );
},
'html'
);
}
function SearchObjectsToAdd(currentFormId)
{
var theMap = { 'class': '{$this->m_sClass}',
'linkageAttr': '{$this->m_sLinkageAttr}',
'linkedClass': '{$this->m_sLinkedClass}',
'objectId': '{$this->m_iObjectId}'
}
if ($('#'+currentFormId+' :input[name=class]').val() != undefined)
{
theMap.linkedClass = $('#'+currentFormId+' :input[name=class]').val();
}
// Gather the parameters from the search form
$('#'+currentFormId+' :input').each(
function(i)
{
if (this.name != '')
{
theMap[this.name] = this.value;
}
}
);
theMap['operation'] = 'searchObjectsToAdd';
// Run the query and display the results
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
$('#SearchResultsToAdd').html(data);
$('#SearchResultsToAdd .listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
},
'html'
);
return false;
}
function DoAddObjects(currentFormId)
{
var theMap = { 'class': '{$this->m_sClass}',
'linkageAttr': '{$this->m_sLinkageAttr}',
'linkedClass': '{$this->m_sLinkedClass}',
'objectId': '{$this->m_iObjectId}'
}
// Gather the parameters from the search form
$('#'+currentFormId+' :input').each(
function(i)
{
if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) )
{
//console.log(this.type);
arrayExpr = /\[\]$/;
if (arrayExpr.test(this.name))
{
// Array
if (theMap[this.name] == undefined)
{
theMap[this.name] = new Array();
}
theMap[this.name].push(this.value);
}
else
{
theMap[this.name] = this.value;
}
}
}
);
theMap['operation'] = 'doAddObjects';
// Run the query and display the results
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
function(data)
{
//console.log('Data: ' + data);
if (data != '')
{
$('#empty_row').remove();
}
$('.listResults tbody').append(data);
$('.listResults').trigger('update');
$('.listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
},
'html'
);
$('#ModalDlg').dialog('close');
return false;
}
function InitForm()
{
// make sure that the form is clean
$('.selection').each( function() { this.checked = false; });
$('#btnRemove').attr('disabled','disabled');
$('#linksToRemove').val('');
}
function SubmitHook()
{
var the_form = this;
SearchObjectsToAdd(the_form.id);
return false;
}
EOF
);
$oP->add_ready_script("InitForm();");
$oFilter = new DBObjectSearch($this->m_sClass);
$oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '=');
$oSet = new DBObjectSet($oFilter);
$aForm = array();
while($oCurrentLink = $oSet->Fetch())
{
$aRow = array();
$key = $oCurrentLink->GetKey();
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode));
$aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink);
}
//var_dump($aTableLabels);
//var_dump($aForm);
$this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm);
$oP->add("<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"RemoveSelected();\" >");
$oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sLinkedClass))."\" onClick=\"AddObjects();\"></span>\n");
$oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"BackToDetails('".$sTargetClass."', ".$this->m_iObjectId.", '', '');\">");
$oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnOk\" type=\"submit\" value=\"".Dict::S('UI:Button:Ok')."\"></span>\n");
$oP->add("<span style=\"clear:both;\"><p>&nbsp;</p></span>\n");
$oP->add("</div>\n");
$oP->add("</form>\n");
if (isset($aExtraParams['StartWithAdd']) && ($aExtraParams['StartWithAdd']))
{
$oP->add_ready_script("AddObjects();");
}
}
protected function GetFormRow($oP, $oLinkedObj, $currentLink = null )
{
$aRow = array();
if(is_object($currentLink))
{
$key = $currentLink->GetKey();
$sNameSuffix = "[$key]"; // To make a tabular form
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$key\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$key]\" value=\"$key\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, $currentLink->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix);
}
}
else
{
// form for creating a new record
$sNameSuffix = "[$currentLink]"; // To make a tabular form
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$currentLink\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$currentLink]\" value=\"$currentLink\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix);
}
}
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
{
$aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode);
}
return $aRow;
}
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
{
$oP->add("<table class=\"listResults\">\n");
// Header
$oP->add("<thead>\n");
$oP->add("<tr>\n");
foreach($aConfig as $sName=>$aDef)
{
$oP->add("<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n");
}
$oP->add("</tr>\n");
$oP->add("</thead>\n");
// Content
$oP->add("</tbody>\n");
if (count($aData) == 0)
{
$oP->add("<tr id=\"empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."</td></td>");
}
else
{
foreach($aData as $iRowId => $aRow)
{
$this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
}
$oP->add("</tbody>\n");
// Footer
$oP->add("</table>\n");
}
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
{
$oP->add("<tr id=\"row_$iRowId\">\n");
foreach($aConfig as $sName=>$void)
{
$oP->add("<td>".$aRow[$sName]."</td>\n");
}
$oP->add("</tr>\n");
}
public function DisplayAddForm(WebPage $oP)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
$sTargetClass = $oAttDef->GetTargetClass();
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
$oP->add("<div class=\"wizContainer\">\n");
//$oP->add("<div class=\"page_header\">\n");
//$oP->add("<h1>".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
//$oP->add("</div>\n");
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$oBlock->Display($oP, 'SearchFormToAdd', array('open' => true));
$oP->Add("<form id=\"ObjectsAddForm\" OnSubmit=\"return DoAddObjects(this.id);\">\n");
$oP->Add("<div id=\"SearchResultsToAdd\">\n");
$oP->Add("<div style=\"height: 100px; background: #fff;border-color:#F6F6F1 #E6E6E1 #E6E6E1 #F6F6F1; border-style:solid; border-width:3px; text-align: center; vertical-align: center;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n");
$oP->Add("</div>\n");
$oP->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ModalDlg').dialog('close');\">&nbsp;&nbsp;<input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">");
$oP->Add("</div>\n");
$oP->Add("</form>\n");
$oP->add_ready_script("$('#ModalDlg').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."'});");
$oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit.uilinksWizard', SubmitHook);");
}
public function SearchObjectsToAdd(WebPage $oP)
{
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())
{
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
//$sTargetClass = $oAttDef->GetTargetClass();
//$oP->Add("<!-- nb of objects to add: ".count($aLinkedObjectIds)." -->\n"); // Just to make sure it's not empty
$aTable = array();
foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $iObjectId);
if (is_object($oLinkedObj))
{
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
$this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId);
}
else
{
echo Dict::Format('UI:Error:Object_Class_Id_NotFound', $this->m_sLinkedClass, $iObjectId);
}
}
//var_dump($aTable);
//$oP->Add("<!-- end of added list -->\n"); // Just to make sure it's not empty
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Store and retrieve user's preferences (i.e persistent per user settings)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/dbobject.class.php');
@@ -50,7 +50,7 @@ class appUserPreferences extends DBObject
self::Load();
}
$aPrefs = self::$oUserPrefs->Get('preferences');
if (isset($aPrefs[$sCode]))
if (array_key_exists($sCode, $aPrefs))
{
return $aPrefs[$sCode];
}
@@ -72,9 +72,16 @@ class appUserPreferences extends DBObject
self::Load();
}
$aPrefs = self::$oUserPrefs->Get('preferences');
$aPrefs[$sCode] = $sValue;
self::$oUserPrefs->Set('preferences', $aPrefs);
self::Save();
if (array_key_exists($sCode, $aPrefs) && ($aPrefs[$sCode] === $sValue))
{
// Do not write it again
}
else
{
$aPrefs[$sCode] = $sValue;
self::$oUserPrefs->Set('preferences', $aPrefs);
self::Save();
}
}
/**
@@ -148,7 +155,9 @@ class appUserPreferences extends DBObject
{
if (self::$oUserPrefs->IsModified())
{
utils::PushArchiveMode(false);
self::$oUserPrefs->DBUpdate();
utils::PopArchiveMode();
}
}
}
@@ -172,7 +181,9 @@ class appUserPreferences extends DBObject
$oObj->Set('preferences', array()); // Default preferences: an empty array
try
{
utils::PushArchiveMode(false);
$oObj->DBInsert();
utils::PopArchiveMode();
}
catch(Exception $e)
{
@@ -207,7 +218,8 @@ class appUserPreferences extends DBObject
*/
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
utils::PushArchiveMode(false);
$this->DBDelete($oDeletionPlan);
utils::PopArchiveMode();
}
}
?>

View File

@@ -1,7 +1,7 @@
<?php
use Html2Text\Html2Text;
use Leafo\ScssPhp\Compiler;
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -22,7 +22,7 @@ use Leafo\ScssPhp\Compiler;
/**
* Static class utils
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -140,6 +140,81 @@ class utils
}
}
protected static $bPageMode = null;
/**
* @var boolean[]
*/
protected static $aModes = array();
public static function InitArchiveMode()
{
if (isset($_SESSION['archive_mode']))
{
$iDefault = $_SESSION['archive_mode'];
}
else
{
$iDefault = 0;
}
// Read and record the value for switching the archive mode
$iCurrent = self::ReadParam('with-archive', $iDefault);
if (isset($_SESSION))
{
$_SESSION['archive_mode'] = $iCurrent;
}
// Read and use the value for the current page (web services)
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
self::$bPageMode = ($iCurrent == 1);
}
/**
* @param boolean $bMode if true then activate archive mode (archived objects are visible), otherwise archived objects are
* hidden (archive = "soft deletion")
*/
public static function PushArchiveMode($bMode)
{
array_push(self::$aModes, $bMode);
}
public static function PopArchiveMode()
{
array_pop(self::$aModes);
}
/**
* @return boolean true if archive mode is enabled
*/
public static function IsArchiveMode()
{
if (count(self::$aModes) > 0)
{
$bRet = end(self::$aModes);
}
else
{
if (self::$bPageMode === null)
{
self::InitArchiveMode();
}
$bRet = self::$bPageMode;
}
return $bRet;
}
/**
* Helper to be called by the GUI and define if the user will see obsolete data (otherwise, the user will have to dig further)
* @return bool
*/
public static function ShowObsoleteData()
{
$bDefault = MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'); // default is false
$bShow = appUserPreferences::GetPref('show_obsolete_data', $bDefault);
if (static::IsArchiveMode())
{
$bShow = true;
}
return $bShow;
}
public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter')
{
@@ -437,6 +512,45 @@ class utils
return $iReturn;
}
/**
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
*
* @param type $value
* @return string
*/
public static function BytesToFriendlyFormat($value)
{
$sReturn = '';
// Kilobytes
if ($value >= 1024)
{
$sReturn = 'K';
$value = $value / 1024;
}
// Megabytes
if ($value >= 1024)
{
$sReturn = 'M';
$value = $value / 1024;
}
// Gigabytes
if ($value >= 1024)
{
$sReturn = 'G';
$value = $value / 1024;
}
// Terabytes
if ($value >= 1024)
{
$sReturn = 'T';
$value = $value / 1024;
}
$value = round($value, 1);
return $value . '' . $sReturn . 'B';
}
/**
* Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
* Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
@@ -501,16 +615,20 @@ class utils
{
if (self::$oConfig == null)
{
$sConfigFile = self::GetConfigFilePath();
if (file_exists($sConfigFile))
{
self::$oConfig = new Config($sConfigFile);
}
else
{
// When executing the setup, the config file may be still missing
self::$oConfig = new Config();
}
self::$oConfig = MetaModel::GetConfig();
if (self::$oConfig == null)
{
$sConfigFile = self::GetConfigFilePath();
if (file_exists($sConfigFile))
{
self::$oConfig = new Config($sConfigFile);
}
else
{
// When executing the setup, the config file may be still missing
self::$oConfig = new Config();
}
}
}
return self::$oConfig;
}
@@ -661,7 +779,7 @@ class utils
}
return $bResult;
}
/**
* Initializes the CAS client
*/
@@ -813,7 +931,7 @@ class utils
*/
public static function GetCachePath()
{
return APPROOT.'data/cache-'.self::GetCurrentEnvironment().'/';
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
}
/**
* Merge standard menu items with plugin provided menus items
@@ -848,9 +966,13 @@ class utils
// Bulk export actions
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')");
if (extension_loaded('gd'))
{
// PDF export requires GD
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
}
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
@@ -946,8 +1068,7 @@ class utils
}
/**
* Returns the absolute URL to the modules root path
* @return string ...
* @return string the absolute URL to the modules root path
*/
static public function GetAbsoluteUrlModulesRoot()
{
@@ -955,33 +1076,67 @@ class utils
return $sUrl;
}
/**
* Returns the URL to a page that will execute the requested module page
*
* To be compatible with this mechanism, the called page must include approot
* with an absolute path OR not include it at all (losing the direct access to the page)
* if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
* require_once(__DIR__.'/../../approot.inc.php');
*
* @return string ...
*/
/**
* To be compatible with this mechanism, the called page must include approot with an absolute path OR not include
* it at all (losing the direct access to the page) :
*
* ```php
* if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
* require_once(__DIR__.'/../../approot.inc.php');
* ```
*
* @param string $sModule
* @param string $sPage
* @param string[] $aArguments
* @param string $sEnvironment
*
* @return string the URL to a page that will execute the requested module page, with query string values url encoded
*
* @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108)
* @see GetAbsoluteUrlExecPage
*/
static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
{
$aArgs = self::GetExecPageArguments($sModule, $sPage, $aArguments, $sEnvironment);
$sArgs = http_build_query($aArgs);
return self::GetAbsoluteUrlExecPage()."?".$sArgs;
}
/**
* @param string $sModule
* @param string $sPage
* @param string[] $aArguments
* @param string $sEnvironment
*
* @return string[] key/value pair for the exec page query string. <b>Warning</b> : values are not url encoded !
* @throws \Exception if one of the argument has a reserved name
*/
static public function GetExecPageArguments($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
{
$sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment;
$aArgs = array();
$aArgs[] = 'exec_module='.$sModule;
$aArgs[] = 'exec_page='.$sPage;
$aArgs[] = 'exec_env='.$sEnvironment;
$aArgs['exec_module'] = $sModule;
$aArgs['exec_page'] = $sPage;
$aArgs['exec_env'] = $sEnvironment;
foreach($aArguments as $sName => $sValue)
{
if (($sName == 'exec_module')||($sName == 'exec_page')||($sName == 'exec_env'))
if (($sName == 'exec_module') || ($sName == 'exec_page') || ($sName == 'exec_env'))
{
throw new Exception("Module page: $sName is a reserved page argument name");
}
$aArgs[] = $sName.'='.urlencode($sValue);
$aArgs[$sName] = $sValue;
}
$sArgs = implode('&', $aArgs);
return self::GetAbsoluteUrlAppRoot().'pages/exec.php?'.$sArgs;
return $aArgs;
}
/**
* @return string
*/
static public function GetAbsoluteUrlExecPage()
{
return self::GetAbsoluteUrlAppRoot().'pages/exec.php';
}
/**
@@ -1275,10 +1430,21 @@ class utils
*/
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
{
// If image size smaller than maximums, we do nothing
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
{
return $oImage;
}
// If gd extension is not loaded, we put a warning in the log and return the image as is
if (extension_loaded('gd') === false)
{
IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
return $oImage;
}
switch($oImage->GetMimeType())
{
case 'image/gif':
@@ -1342,5 +1508,349 @@ class utils
return $oResampledImage;
}
}
}
/**
* Create a 128 bit UUID in the format: {########-####-####-####-############}
*
* Note: this method can be run from the command line as well as from the web server.
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
* consider using open_ssl or PHP 7 methods.
* @param string $sPrefix
* @return string
*/
static public function CreateUUID($sPrefix = '')
{
$uid = uniqid("", true);
$data = $sPrefix;
$data .= __FILE__;
$data .= mt_rand();
$hash = strtoupper(hash('ripemd128', $uid . md5($data)));
$sUUID = '{' .
substr($hash, 0, 8) .
'-' .
substr($hash, 8, 4) .
'-' .
substr($hash, 12, 4) .
'-' .
substr($hash, 16, 4) .
'-' .
substr($hash, 20, 12) .
'}';
return $sUUID;
}
/**
* Returns the name of the module containing the file where the call to this function is made
* or an empty string if no such module is found (or not called within a module file)
* @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
* @return string
*/
static public function GetCurrentModuleName($iCallDepth = 0)
{
$sCurrentModuleName = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleName = $sModuleName;
break;
}
}
}
return $sCurrentModuleName;
}
/**
* Returns the relative (to APPROOT) path of the root directory of the module containing the file where the call to this function is made
* or an empty string if no such module is found (or not called within a module file)
* @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
* @return string
*/
static public function GetCurrentModuleDir($iCallDepth)
{
$sCurrentModuleDir = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleDir = basename($sRootDir);
break;
}
}
}
return $sCurrentModuleDir;
}
/**
* Returns the base URL for all files in the current module from which this method is called
* or an empty string if no such module is found (or not called within a module file)
* @return string
*/
static public function GetCurrentModuleUrl()
{
$sDir = static::GetCurrentModuleDir(1);
if ( $sDir !== '')
{
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
}
/**
* Get the value of a given setting for the current module
* @param string $sProperty The name of the property to retrieve
* @param mixed $defaultvalue
* @return mixed
*/
static public function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{
$sModuleName = static::GetCurrentModuleName(1);
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
}
/**
* Get the compiled version of a given module, as it was seen by the compiler
* @param string $sModuleName
* @return string|NULL
*/
static public function GetCompiledModuleVersion($sModuleName)
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
}
/**
* Check if the given path/url is an http(s) URL
* @param string $sPath
* @return boolean
*/
public static function IsURL($sPath)
{
$bRet = false;
if ((substr($sPath, 0, 7) == 'http://') || (substr($sPath, 0, 8) == 'https://') || (substr($sPath, 0, 8) == 'ftp://'))
{
$bRet = true;
}
return $bRet;
}
/**
* Check if the given URL is a link to download a document/image on the CURRENT iTop
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
* @param string $sPath
* @return false|ormDocument
* @throws Exception
*/
public static function IsSelfURL($sPath)
{
$result = false;
$sPageUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php';
if (substr($sPath, 0, strlen($sPageUrl)) == $sPageUrl)
{
// If the URL is an URL pointing to this instance of iTop, then
// extract the "query" part of the URL and analyze it
$sQuery = parse_url($sPath, PHP_URL_QUERY);
if ($sQuery !== null)
{
$aParams = array();
foreach(explode('&', $sQuery) as $sChunk)
{
$aParts = explode('=', $sChunk);
if (count($aParts) != 2) continue;
$aParams[$aParts[0]] = urldecode($aParts[1]);
}
$result = array_key_exists('operation', $aParams) && array_key_exists('class', $aParams) && array_key_exists('id', $aParams) && array_key_exists('field', $aParams) && ($aParams['operation'] == 'download_document');
if ($result)
{
// This is a 'download_document' operation, let's retrieve the document directly from the database
$sClass = $aParams['class'];
$iKey = $aParams['id'];
$sAttCode = $aParams['field'];
$oObj = MetaModel::GetObject($sClass, $iKey, false /* must exist */); // Users rights apply here !!
if ($oObj)
{
/**
* @var ormDocument $result
*/
$result = clone $oObj->Get($sAttCode);
return $result;
}
}
}
throw new Exception('Invalid URL. This iTop URL is not pointing to a valid Document/Image.');
}
return $result;
}
/**
* Read the content of a file (and retrieve its MIME type) from either:
* - an URL pointing to a blob (image/document) on the current iTop server
* - an http(s) URL
* - the local file system (but only if you are an administrator)
* @param string $sPath
* @return ormDocument|null
* @throws Exception
*/
public static function FileGetContentsAndMIMEType($sPath)
{
$oUploadedDoc = null;
$aKnownExtensions = array(
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'dot' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'vsd' => 'application/x-visio',
'vdx' => 'application/visio.drawing',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'zip' => 'application/zip',
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'exe' => 'application/octet-stream'
);
$sData = null;
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
$sFileName = 'uploaded-file'; // Default name for downloaded-files
$sExtension = '.txt'; // Default file extension in case we don't know the MIME Type
if(empty($sPath))
{
// Empty path (NULL or '') means that there is no input, making an empty document.
$oUploadedDoc = new ormDocument('', '', '');
}
elseif (static::IsURL($sPath))
{
if ($oUploadedDoc = static::IsSelfURL($sPath))
{
// Nothing more to do, we've got it !!
}
else
{
// Remote file, let's use the HTTP headers to find the MIME Type
$sData = @file_get_contents($sPath);
if ($sData === false)
{
throw new Exception("Failed to load the file from the URL '$sPath'.");
}
else
{
if (isset($http_response_header))
{
$aHeaders = static::ParseHeaders($http_response_header);
$sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream';
// Compute the file extension from the MIME Type
foreach($aKnownExtensions as $sExtValue => $sMime)
{
if ($sMime === $sMimeType)
{
$sExtension = '.'.$sExtValue;
break;
}
}
}
$sFileName .= $sExtension;
}
$oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
}
}
else if (UserRights::IsAdministrator())
{
// Only administrators are allowed to read local files
$sData = @file_get_contents($sPath);
if ($sData === false)
{
throw new Exception("Failed to load the file '$sPath'. The file does not exist or the current process is not allowed to access it.");
}
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
$sFileName = basename($sPath);
if (array_key_exists($sExtension, $aKnownExtensions))
{
$sMimeType = $aKnownExtensions[$sExtension];
}
else if (extension_loaded('fileinfo'))
{
$finfo = new finfo(FILEINFO_MIME);
$sMimeType = $finfo->file($sPath);
}
$oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
}
return $oUploadedDoc;
}
protected static function ParseHeaders($aHeaders)
{
$aCleanHeaders = array();
foreach( $aHeaders as $sKey => $sValue )
{
$aTokens = explode(':', $sValue, 2);
if(isset($aTokens[1]))
{
$aCleanHeaders[trim($aTokens[0])] = trim($aTokens[1]);
}
else
{
// The header is not in the form Header-Code: Value
$aCleanHeaders[] = $sValue; // Store the value as-is
$aMatches = array();
// Check if it's not the HTTP response code
if( preg_match("|HTTP/[0-9\.]+\s+([0-9]+)|", $sValue, $aMatches) )
{
$aCleanHeaders['reponse_code'] = intval($aMatches[1]);
}
}
}
return $aCleanHeaders;
}
/**
* Check if the given class if configured as a high cardinality class.
*
* @param $sClass
*
* @return bool
*/
public static function IsHighCardinality($sClass)
{
$aHugeClasses = MetaModel::GetConfig()->Get('high_cardinality_classes');
return in_array($sClass, $aHugeClasses);
}
}

View File

@@ -345,27 +345,40 @@ class WebPage implements Page
*/
public function GetDetails($aFields)
{
$sHtml = "<table class=\"details\">\n";
$sHtml .= "<tbody>\n";
$sHtml = "<div class=\"details\">\n";
foreach($aFields as $aAttrib)
{
$sHtml .= "<tr>\n";
$sDataAttCode = isset($aAttrib['attcode']) ? "data-attcode=\"{$aAttrib['attcode']}\"" : '';
$sLayout = isset($aAttrib['layout']) ? $aAttrib['layout'] : 'small';
$sHtml .= "<div class=\"field_container field_{$sLayout}\" $sDataAttCode>\n";
$sHtml .= "<div class=\"field_label label\">{$aAttrib['label']}</div>\n";
$sHtml .= "<div class=\"field_data\">\n";
// By Rom, for csv import, proposed to show several values for column selection
if (is_array($aAttrib['value']))
{
$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".implode("</td><td>", $aAttrib['value'])."</td>\n";
$sHtml .= "<div class=\"field_value\">".implode("</div><div>", $aAttrib['value'])."</div>\n";
}
else
{
$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".$aAttrib['value']."</td>\n";
$sHtml .= "<div class=\"field_value\">".$aAttrib['value']."</div>\n";
}
$sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : '&nbsp;';
$sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : '&nbsp;';
$sHtml .= "<td>$sComment</td><td>$sInfo</td>\n";
$sHtml .= "</tr>\n";
// Checking if we should add comments & infos
$sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : '';
$sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : '';
if($sComment !== '')
{
$sHtml .= "<div class=\"field_comments\">$sComment</div>\n";
}
if($sInfo !== '')
{
$sHtml .= "<div class=\"field_infos\">$sInfo</div>\n";
}
$sHtml .= "</div>\n";
$sHtml .= "</div>\n";
}
$sHtml .= "</tbody>\n";
$sHtml .= "</table>\n";
$sHtml .= "</div>\n";
return $sHtml;
}
@@ -734,7 +747,7 @@ class WebPage implements Page
{
foreach ($aActions as $aAction)
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
$sClass = isset($aAction['css_classes']) ? ' class="'.implode(' ', $aAction['css_classes']).'"' : '';
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
if (empty($aAction['url']))

View File

@@ -84,6 +84,18 @@ class WizardHelper
$oTargetObj = MetaModel::GetObject($sLinkedAttDef->GetTargetClass(), $aLinkedObject[$sLinkedAttCode]);
$oLinkedObj->Set($sLinkedAttCode, $oTargetObj);
}
elseif($sLinkedAttDef instanceof AttributeDateTime)
{
$sDate = $aLinkedObject[$sLinkedAttCode];
if($sDate !== null && $sDate !== '')
{
$oDateTimeFormat = AttributeDateTime::GetFormat();
$oDate = $oDateTimeFormat->Parse($sDate);
$sDate = $oDate->format('Y-m-d H:i:s');
}
$oLinkedObj->Set($sLinkedAttCode, $sDate);
}
else
{
$oLinkedObj->Set($sLinkedAttCode, $aLinkedObject[$sLinkedAttCode]);
@@ -138,6 +150,22 @@ class WizardHelper
$oObj->Set($sAttCode, $value);
}
}
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
if ($value != null)
{
$oDate = $oAttDef->GetFormat()->Parse($value);
if ($oDate instanceof DateTime)
{
$value = $oDate->format($oAttDef->GetInternalFormat());
}
else
{
$value = null;
}
}
$oObj->Set($sAttCode, $value);
}
else
{
$oObj->Set($sAttCode, $value);
@@ -254,11 +282,21 @@ class WizardHelper
{
return $this->m_aData['m_sClass'];
}
public function GetFormPrefix()
{
return $this->m_aData['m_sFormPrefix'];
}
public function GetFormPrefix()
{
return $this->m_aData['m_sFormPrefix'];
}
public function GetInitialState()
{
return isset($this->m_aData['m_sInitialState']) ? $this->m_aData['m_sInitialState'] : null;
}
public function GetStimulus()
{
return isset($this->m_aData['m_sStimulus']) ? $this->m_aData['m_sStimulus'] : null;
}
public function GetIdForField($sFieldName)
{

98
core/apc-compat.php Normal file
View File

@@ -0,0 +1,98 @@
<?php
// Copyright (C) 2016-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
// Emulate the API of APC, over APCU
// Note: for PHP < 7, this compatibility used to be provided by APCU itself (if compiled with some options)
// for PHP 7+, it can be provided by the mean of apcu_bc, which is not so simple to install
// The current emulation aims at skipping this complexity
if (!function_exists('apc_store') && function_exists('apcu_store'))
{
function apc_add($key, $var, $ttl = 0)
{
return apcu_add($key, $var, $ttl);
}
function apc_cache_info($cache_type = '', $limited = false)
{
return apcu_cache_info($limited);
}
function apc_cas($key, $old, $new)
{
return apcu_cas($key, $old, $new);
}
function apc_clear_cache($cache_type = '')
{
return apcu_clear_cache();
}
function apc_dec($key, $step = 1, &$success = null)
{
apcu_dec($key, $step, $success);
}
function apc_delete($key)
{
return apcu_delete($key);
}
function apc_exists($keys)
{
return apcu_exists($keys);
}
function apc_fetch($key)
{
return apcu_fetch($key);
}
function apc_inc($key, $step = 1, &$success = null)
{
apcu_inc($key, $step, $success);
}
function apc_sma_info($limited = false)
{
return apcu_sma_info($limited);
}
function apc_store($key, $var, $ttl = 0)
{
return apcu_store($key, $var, $ttl);
}
}
/**
* Returns user cache info... beware of the format of the returned structure that may vary (See usages)
* @return array
*/
function apc_cache_info_compat()
{
if (!function_exists('apc_cache_info')) return array();
$oFunction = new ReflectionFunction('apc_cache_info');
if ($oFunction->getNumberOfParameters() != 2)
{
// Beware: APCu behaves slightly differently from APC !!
// Worse: the compatibility layer integrated into APC differs from apcu-bc (testing the number of parameters is a must)
// In CLI mode (PHP > 7) apc_cache_info returns null and outputs an error message.
$aCacheUserData = @apc_cache_info();
}
else
{
$aCacheUserData = @apc_cache_info('user');
}
return $aCacheUserData;
}
// Cache emulation
if (!function_exists('apc_store'))
{
require_once(APPROOT.'core/apc-emulation.php');
}

301
core/apc-emulation.php Normal file
View File

@@ -0,0 +1,301 @@
<?php
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
/**
* Date: 27/09/2017
*/
/**
* @param string $cache_type
* @param bool $limited
* @return array|bool
*/
function apc_cache_info($cache_type = '', $limited = false)
{
$aInfo = array();
$sRootCacheDir = apc_emul_get_cache_filename('');
$aInfo['cache_list'] = apc_emul_get_cache_entries($sRootCacheDir);
return $aInfo;
}
function apc_emul_get_cache_entries($sEntry)
{
$aResult = array();
if (is_dir($sEntry))
{
$aFiles = array_diff(scandir($sEntry), array('.', '..'));
foreach($aFiles as $sFile)
{
$sSubFile = $sEntry.'/'.$sFile;
$aResult = array_merge($aResult, apc_emul_get_cache_entries($sSubFile));
}
}
else
{
$sKey = basename($sEntry);
if (strpos($sKey, '-') === 0)
{
$sKey = substr($sKey, 1);
}
$aResult[] = array('info' => $sKey);
}
return $aResult;
}
/**
* @param array|string $key
* @param $var
* @param int $ttl
* @return array|bool
*/
function apc_store($key, $var = NULL, $ttl = 0)
{
if (is_array($key))
{
$aResult = array();
foreach($key as $sKey => $value)
{
$aResult[] = apc_emul_store_unit($sKey, $value, $ttl);
}
return $aResult;
}
return apc_emul_store_unit($key, $var, $ttl);
}
/**
* @param string $sKey
* @param $value
* @param int $iTTL time to live
* @return bool
*/
function apc_emul_store_unit($sKey, $value, $iTTL)
{
if ($iTTL > 0)
{
// hint for ttl management
$sKey = '-'.$sKey;
}
$sFilename = apc_emul_get_cache_filename($sKey);
// try to create the folder
$sDirname = dirname($sFilename);
if (!file_exists($sDirname))
{
if (!@mkdir($sDirname, 0755, true))
{
return false;
}
}
$bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false);
apc_emul_manage_new_entry($sFilename);
return $bRes;
}
/**
* @param $key string|array
* @return mixed
*/
function apc_fetch($key)
{
if (is_array($key))
{
$aResult = array();
foreach($key as $sKey)
{
$aResult[$sKey] = apc_emul_fetch_unit($sKey);
}
return $aResult;
}
return apc_emul_fetch_unit($key);
}
/**
* @param $sKey
* @return bool|mixed
*/
function apc_emul_fetch_unit($sKey)
{
// Try the 'TTLed' version
$sValue = apc_emul_readcache_locked(apc_emul_get_cache_filename('-'.$sKey));
if ($sValue === false)
{
$sValue = apc_emul_readcache_locked(apc_emul_get_cache_filename($sKey));
if ($sValue === false)
{
return false;
}
}
$oRes = @unserialize($sValue);
return $oRes;
}
function apc_emul_readcache_locked($sFilename)
{
$file = @fopen($sFilename, 'r');
if ($file === false)
{
return false;
}
flock($file, LOCK_SH);
$sContent = @fread($file, @filesize($sFilename));
flock($file, LOCK_UN);
fclose($file);
return $sContent;
}
/**
* @param string $cache_type
* @return bool
*/
function apc_clear_cache($cache_type = '')
{
$sRootCacheDir = apc_emul_get_cache_filename('');
apc_emul_delete_entry($sRootCacheDir);
return true;
}
function apc_emul_delete_entry($sCache)
{
if (is_dir($sCache))
{
$aFiles = array_diff(scandir($sCache), array('.', '..'));
foreach($aFiles as $sFile)
{
$sSubFile = $sCache.'/'.$sFile;
if (!apc_emul_delete_entry($sSubFile))
{
return false;
}
}
if (!@rmdir($sCache))
{
return false;
}
}
else
{
if (!@unlink($sCache))
{
return false;
}
}
return true;
}
/**
* @param $key
* @return bool|string[]
*/
function apc_delete($key)
{
return apc_emul_delete_entry(apc_emul_get_cache_filename($key));
}
function apc_emul_get_cache_filename($sKey)
{
$sPath = str_replace(array(' ', '/', '\\', '.'), '-', $sKey);
return utils::GetCachePath().'apc-emul/'.$sPath;
}
/** Manage the cache files when a new cache entry is added
* @param string $sNewFilename new cache file added
*/
function apc_emul_manage_new_entry($sNewFilename)
{
// Check only once per request
static $aFilesByTime = null;
static $iFileCount = 0;
$iMaxFiles = MetaModel::GetConfig()->Get('apc_cache_emulation.max_entries');
if ($iMaxFiles == 0)
{
return;
}
if (!$aFilesByTime)
{
$sRootCacheDir = apc_emul_get_cache_filename('');
$aFilesByTime = apc_emul_list_files_time($sRootCacheDir);
$iFileCount = count($aFilesByTime);
if ($iMaxFiles !== 0)
{
asort($aFilesByTime);
}
}
else
{
$aFilesByTime[$sNewFilename] = time();
$iFileCount++;
}
if ($iFileCount > $iMaxFiles)
{
$iFileNbToRemove = $iFileCount - $iMaxFiles;
foreach($aFilesByTime as $sFileToRemove => $iTime)
{
@unlink($sFileToRemove);
if ($iFileNbToRemove-- === 0)
{
break;
}
}
$aFilesByTime = array_slice($aFilesByTime, $iFileCount - $iMaxFiles, null, true);
$iFileCount = $iMaxFiles;
}
}
/** Get the list of files with their associated access time
* @param string $sCheck Directory to scan
* @param array $aFilesByTime used by recursion
* @return array
*/
function apc_emul_list_files_time($sCheck, &$aFilesByTime = array())
{
// Garbage collection
$aFiles = array_diff(@scandir($sCheck), array('.', '..'));
foreach($aFiles as $sFile)
{
$sSubFile = $sCheck.'/'.$sFile;
if (is_dir($sSubFile))
{
apc_emul_list_files_time($sSubFile, $aFilesByTime);
}
else
{
$iTime = apc_emul_get_file_time($sSubFile);
if ($iTime !== false)
{
$aFilesByTime[$sSubFile] = $iTime;
}
}
}
return $aFilesByTime;
}
/** Get the file access time if TTL is managed
* @param string $sFilename
* @return bool|int returns the file atime or false if not relevant
*/
function apc_emul_get_file_time($sFilename)
{
if (strpos(basename($sFilename), '-') === 0)
{
return @fileatime($sFilename);
}
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ MetaModel::IncludeModule('application/user.preferences.class.inc.php');
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
MetaModel::IncludeModule('application/query.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
MetaModel::IncludeModule('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');

55
core/background.inc.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
// Copyright (C) 2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Tasks performed in the background
*
* @copyright Copyright (C) 2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ObsolescenceDateUpdater implements iBackgroundProcess
{
public function GetPeriodicity()
{
return MetaModel::GetConfig()->Get('obsolescence.date_update_interval'); // 10 mn
}
public function Process($iUnixTimeLimit)
{
$iCountSet = 0;
$iCountReset = 0;
$iClasses = 0;
foreach (MetaModel::EnumObsoletableClasses() as $sClass)
{
$oObsoletedToday = new DBObjectSearch($sClass);
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '=');
$oObsoletedToday->AddCondition('obsolescence_date', null, '=');
$sToday = date(AttributeDate::GetSQLFormat());
$iCountSet += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => $sToday));
$oObsoletedToday = new DBObjectSearch($sClass);
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '!=');
$oObsoletedToday->AddCondition('obsolescence_date', null, '!=');
$iCountReset += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => null));
}
echo "Obsolescence date updated (classes: $iClasses ; set: $iCountSet ; reset: $iCountReset)\n";
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -471,6 +471,11 @@ class BulkChange
// skip the private key, if any
if ($sAttCode == 'id') continue;
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
// skip reconciliation keys
if (!$oAttDef->IsWritable() && in_array($sAttCode, $this->m_aReconcilKeys)){ continue; }
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
$aReasons = array();
$iFlags = $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
@@ -828,8 +833,8 @@ class BulkChange
$sFormat = $sDateFormat;
}
$oFormat = new DateTimeFormat($sFormat);
$sRegExp = $oFormat->ToRegExpr();
if (!preg_match('/'.$sRegExp.'/', $this->m_aData[$iRow][$iCol]))
$sRegExp = $oFormat->ToRegExpr('/');
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
{
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
}
@@ -1306,5 +1311,3 @@ EOF
}
}
?>

View File

@@ -112,7 +112,9 @@ class BulkExportResultGC implements iBackgroundProcess
}
$iProcessed++;
@unlink($oResult->Get('temp_file_path'));
utils::PushArchiveMode(false);
$oResult->DBDelete();
utils::PopArchiveMode();
}
return "Cleaned $iProcessed old export results(s).";
}
@@ -306,7 +308,10 @@ abstract class BulkExport
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
}
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
return $this->oBulkExportResult->DBWrite();
utils::PushArchiveMode(false);
$ret = $this->oBulkExportResult->DBWrite();
utils::PopArchiveMode();
return $ret;
}
public function Cleanup()
@@ -318,7 +323,9 @@ abstract class BulkExport
{
@unlink($sFilename);
}
utils::PushArchiveMode(false);
$this->oBulkExportResult->DBDelete();
utils::PopArchiveMode();
}
}
@@ -412,7 +419,11 @@ abstract class BulkExport
// The built-in exports
require_once(APPROOT.'core/tabularbulkexport.class.inc.php');
require_once(APPROOT.'core/htmlbulkexport.class.inc.php');
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
if (extension_loaded('gd'))
{
// PDF export - via TCPDF - requires GD
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
}
require_once(APPROOT.'core/csvbulkexport.class.inc.php');
require_once(APPROOT.'core/excelbulkexport.class.inc.php');
require_once(APPROOT.'core/spreadsheetbulkexport.class.inc.php');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class cmdbObject
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -231,6 +231,202 @@ abstract class CMDBObject extends DBObject
$iId = $oMyChangeOp->DBInsertNoReload();
}
/**
* @param $sAttCode
* @param $original Original value
* @param $value Current value
*/
protected function RecordAttChange($sAttCode, $original, $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalField()) return;
if ($oAttDef->IsLinkSet()) return;
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) return;
if ($oAttDef instanceOf AttributeOneWayPassword)
{
// One Way encrypted passwords' history is stored -one way- encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = '';
}
$oMyChangeOp->Set("prev_pwd", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeEncryptedString)
{
// Encrypted string history is stored encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = '';
}
$oMyChangeOp->Set("prevstring", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeBlob)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = new ormDocument();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeStopWatch)
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
{
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
if ($item_value != $item_original)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
$oMyChangeOp->Set("oldvalue", $item_original);
$oMyChangeOp->Set("newvalue", $item_value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
}
}
elseif ($oAttDef instanceOf AttributeCaseLog)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeLongText)
{
// Data blobs
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (!is_null($original) && ($original instanceof ormCaseLog))
{
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeText)
{
// Data blobs
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (!is_null($original) && ($original instanceof ormCaseLog))
{
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeBoolean)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
{
// Hierarchical keys
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeCustomFields)
{
// Custom fields
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeURL)
{
// URLs
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
else
{
// Scalars
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
}
/**
* @param array $aValues
* @param array $aOrigValues
*/
protected function RecordAttChanges(array $aValues, array $aOrigValues)
{
parent::RecordAttChanges($aValues, $aOrigValues);
@@ -239,11 +435,6 @@ abstract class CMDBObject extends DBObject
//
foreach ($aValues as $sAttCode=> $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalField()) continue;
if ($oAttDef->IsLinkSet()) continue;
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) continue;
if (array_key_exists($sAttCode, $aOrigValues))
{
$original = $aOrigValues[$sAttCode];
@@ -252,185 +443,7 @@ abstract class CMDBObject extends DBObject
{
$original = null;
}
if ($oAttDef instanceOf AttributeOneWayPassword)
{
// One Way encrypted passwords' history is stored -one way- encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = '';
}
$oMyChangeOp->Set("prev_pwd", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeEncryptedString)
{
// Encrypted string history is stored encrypted
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = '';
}
$oMyChangeOp->Set("prevstring", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeBlob)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (is_null($original))
{
$original = new ormDocument();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeStopWatch)
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
{
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
if ($item_value != $item_original)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
$oMyChangeOp->Set("oldvalue", $item_original);
$oMyChangeOp->Set("newvalue", $item_value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
}
}
elseif ($oAttDef instanceOf AttributeCaseLog)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeLongText)
{
// Data blobs
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (!is_null($original) && ($original instanceof ormCaseLog))
{
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeText)
{
// Data blobs
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (!is_null($original) && ($original instanceof ormCaseLog))
{
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeBoolean)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
{
// Hierarchical keys
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeCustomFields)
{
// Custom fields
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeURL)
{
// URLs
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
else
{
// Scalars
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
$this->RecordAttChange($sAttCode, $original, $value);
}
}
@@ -596,6 +609,34 @@ abstract class CMDBObject extends DBObject
}
return $ret;
}
public function DBArchive()
{
// Note: do the job anyway, so as to repair any DB discrepancy
$bOriginal = $this->Get('archive_flag');
parent::DBArchive();
if (!$bOriginal)
{
utils::PushArchiveMode(false);
$this->RecordAttChange('archive_flag', false, true);
utils::PopArchiveMode();
}
}
public function DBUnarchive()
{
// Note: do the job anyway, so as to repair any DB discrepancy
$bOriginal = $this->Get('archive_flag');
parent::DBUnarchive();
if ($bOriginal)
{
utils::PushArchiveMode(false);
$this->RecordAttChange('archive_flag', true, false);
utils::PopArchiveMode();
}
}
}
@@ -649,5 +690,5 @@ class CMDBObjectSet extends DBObjectSet
$oRetSet->AddObjectExtended($aObjectsByClassAlias);
}
return $oRetSet;
}
}
}

View File

@@ -33,13 +33,15 @@ class MySQLException extends CoreException
{
if ($oException != null)
{
$aContext['mysql_error'] = $oException->getCode();
$aContext['mysql_errno'] = $oException->getMessage();
$aContext['mysql_errno'] = $oException->getCode();
$this->code = $oException->getCode();
$aContext['mysql_error'] = $oException->getMessage();
}
else
{
$aContext['mysql_error'] = CMDBSource::GetError();
$aContext['mysql_errno'] = CMDBSource::GetErrNo();
$this->code = CMDBSource::GetErrNo();
$aContext['mysql_error'] = CMDBSource::GetError();
}
parent::__construct($sIssue, $aContext);
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,8 +18,9 @@
define('ITOP_APPLICATION', 'iTop');
define('ITOP_VERSION', '$ITOP_VERSION$');
define('ITOP_REVISION', '$WCREV$');
define('ITOP_APPLICATION_SHORT', 'iTop');
define('ITOP_VERSION', '2.4.0');
define('ITOP_REVISION', 'svn');
define('ITOP_BUILD_DATE', '$WCNOW$');
define('ACCESS_USER_WRITE', 1);
@@ -48,7 +49,6 @@ define ('DEFAULT_LOG_GLOBAL', true);
define ('DEFAULT_LOG_NOTIFICATION', true);
define ('DEFAULT_LOG_ISSUE', true);
define ('DEFAULT_LOG_WEB_SERVICE', true);
define ('DEFAULT_LOG_QUERIES', false);
define ('DEFAULT_QUERY_CACHE_ENABLED', true);
@@ -195,14 +195,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'allow_target_creation' => array(
'type' => 'bool',
'description' => 'Displays the + button on external keys to create target objects',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'allow_menu_on_linkset' => array(
'type' => 'bool',
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'allow_target_creation' => array(
'type' => 'bool',
'description' => 'Displays the + button on external keys to create target objects',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
// Levels that trigger a confirmation in the CSV import/synchro wizard
'csv_import_min_object_confirmation' => array(
'type' => 'integer',
@@ -246,7 +254,7 @@ class Config
),
'access_mode' => array(
'type' => 'integer',
'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)',
'description' => 'Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3',
'default' => ACCESS_FULL,
'value' => ACCESS_FULL,
'source_of_value' => '',
@@ -428,6 +436,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'apc_cache_emulation.max_entries' => array(
'type' => 'integer',
'description' => 'Maximum number of cache entries (0 means no limit)',
'default' => 1000,
'value' => 1000,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'timezone' => array(
'type' => 'string',
'description' => 'Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP',
@@ -930,6 +946,46 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'obsolescence.show_obsolete_data' => array(
'type' => 'bool',
'description' => 'Default value for the user preference "show obsolete data"',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'obsolescence.date_update_interval' => array(
'type' => 'integer',
'description' => 'Delay in seconds between two refreshes of the obsolescence dates.',
'default' => 600,
'value' => 600,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'disable_attachments_download_legacy_portal' => array(
'type' => 'bool',
'description' => 'Disable attachments download from legacy portal',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'optimize_requests_for_join_count' => array(
'type' => 'bool',
'description' => 'Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'high_cardinality_classes' => array(
'type' => 'array',
'description' => 'List of classes with high cardinality (force auto-complete mode)',
'default' => array(),
'value' => array(),
'source_of_value' => '',
'show_in_conf_sample' => true,
),
);
public function IsProperty($sPropCode)
@@ -990,7 +1046,6 @@ class Config
protected $m_bLogNotification;
protected $m_bLogIssue;
protected $m_bLogWebService;
protected $m_bLogQueries; // private setting
protected $m_bQueryCacheEnabled; // private setting
/**
@@ -1085,7 +1140,6 @@ class Config
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
$this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY;
$this->m_aCharsets = array();
$this->m_bLogQueries = DEFAULT_LOG_QUERIES;
$this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED;
$this->m_aModuleSettings = array();
@@ -1198,7 +1252,6 @@ class Config
$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
$this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE;
$this->m_bLogQueries = isset($MySettings['log_queries']) ? (bool) trim($MySettings['log_queries']) : DEFAULT_LOG_QUERIES;
$this->m_bQueryCacheEnabled = isset($MySettings['query_cache_enabled']) ? (bool) trim($MySettings['query_cache_enabled']) : DEFAULT_QUERY_CACHE_ENABLED;
$this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT;
@@ -1317,7 +1370,7 @@ class Config
public function GetLogQueries()
{
return $this->m_bLogQueries;
return false;
}
public function GetQueryCacheEnabled()
@@ -1519,7 +1572,6 @@ class Config
$aSettings['log_notification'] = $this->m_bLogNotification;
$aSettings['log_issue'] = $this->m_bLogIssue;
$aSettings['log_web_service'] = $this->m_bLogWebService;
$aSettings['log_queries'] = $this->m_bLogQueries;
$aSettings['query_cache_enabled'] = $this->m_bQueryCacheEnabled;
$aSettings['min_display_limit'] = $this->m_iMinDisplayLimit;
$aSettings['max_display_limit'] = $this->m_iMaxDisplayLimit;
@@ -1578,7 +1630,6 @@ class Config
'log_notification' => $this->m_bLogNotification,
'log_issue' => $this->m_bLogIssue,
'log_web_service' => $this->m_bLogWebService,
'log_queries' => $this->m_bLogQueries,
'query_cache_enabled' => $this->m_bQueryCacheEnabled,
'secure_connection_required' => $this->m_bSecureConnectionRequired,
);

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2016 Combodo SARL
// Copyright (C) 2016-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -29,13 +29,13 @@
*
* if (ContextTag::Check('GUI:Portal'))
*
* @copyright Copyright (C) 2016 Combodo SARL
* @copyright Copyright (C) 2016-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ContextTag
{
protected static $aStack;
protected static $aStack = array();
/**
* Store a context tag on the stack

View File

@@ -112,4 +112,13 @@ class SecurityException extends CoreException
{
}
/**
* Throwned when querying on an object that exists in the database but is archived
*
* @see N.1108
*/
class ArchivedObjectException extends CoreException
{
}
?>

View File

@@ -413,10 +413,16 @@ EOF
/**
* Get the regular expression to (approximately) validate a date/time for the current format
* The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
* @param string $sDelimiter Surround the regexp (and escape) if needed
* @return string The regular expression in PCRE syntax
*/
public function ToRegExpr()
public function ToRegExpr($sDelimiter = null)
{
return '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]/:').'$';
$sRet = '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]:').'$';
if ($sDelimiter !== null)
{
$sRet = $sDelimiter.str_replace($sDelimiter, '\\'.$sDelimiter, $sRet).$sDelimiter;
}
return $sRet;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -116,7 +116,7 @@ abstract class DBObject implements iDisplay
// set default values
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
{
$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
$this->m_aCurrValues[$sAttCode] = $this->GetDefaultValue($sAttCode);
$this->m_aOrigValues[$sAttCode] = null;
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
{
@@ -192,10 +192,14 @@ abstract class DBObject implements iDisplay
return true;
}
/**
* @param bool $bAllowAllData DEPRECATED: the reload must never fail!
* @throws CoreException
*/
public function Reload($bAllowAllData = false)
{
assert($this->m_bIsInDB);
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, $bAllowAllData/* in the future $this->m_bAllowAllData ??*/);
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false /* must be found */, true /* AllowAllData */);
if (empty($aRow))
{
throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
@@ -208,23 +212,7 @@ abstract class DBObject implements iDisplay
{
if (!$oAttDef->IsLinkSet()) continue;
// Load the link information
$sLinkClass = $oAttDef->GetLinkedClass();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
// The class to target is not the current class, because if this is a derived class,
// it may differ from the target class, then things start to become confusing
$oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe);
$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
$oMyselfSearch = new DBObjectSearch($sMyClass);
$oMyselfSearch->AddCondition('id', $this->m_iKey, '=');
$oLinkSearch = new DBObjectSearch($sLinkClass);
$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
$oLinks = new DBObjectSet($oLinkSearch);
$this->m_aCurrValues[$sAttCode] = $oLinks;
$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
$this->m_aOrigValues[$sAttCode] = clone $this->m_aCurrValues[$sAttCode];
$this->m_aLoadedAtt[$sAttCode] = true;
}
@@ -348,7 +336,14 @@ abstract class DBObject implements iDisplay
}
return $bFullyLoaded;
}
protected function _Set($sAttCode, $value)
{
$this->m_aCurrValues[$sAttCode] = $value;
$this->m_aTouchedAtt[$sAttCode] = true;
unset($this->m_aModifiedAtt[$sAttCode]);
}
public function Set($sAttCode, $value)
{
if ($sAttCode == 'finalclass')
@@ -356,8 +351,15 @@ abstract class DBObject implements iDisplay
// Ignore it - this attribute is set upon object creation and that's it
return false;
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (!$oAttDef->IsWritable())
{
$sClass = get_class($this);
throw new Exception("Attempting to set the value on the read-only attribute $sClass::$sAttCode");
}
if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
{
// First time Set is called... ensure that the object gets fully loaded
@@ -380,7 +382,7 @@ abstract class DBObject implements iDisplay
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
$this->m_aLoadedAtt[$sCode] = true;
@@ -393,48 +395,28 @@ abstract class DBObject implements iDisplay
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
unset($this->m_aLoadedAtt[$sCode]);
}
}
}
}
if($oAttDef->IsLinkSet())
if ($oAttDef->IsLinkSet() && ($value != null))
{
if (is_null($value))
{
// Normalize
$value = DBObjectSet::FromScratch($oAttDef->GetLinkedClass());
}
else
{
if ((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
{
throw new CoreUnexpectedValue("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
}
}
$oObjectSet = $value;
$sSetClass = $oObjectSet->GetClass();
$sLinkClass = $oAttDef->GetLinkedClass();
// not working fine :-( if (!is_subclass_of($sSetClass, $sLinkClass))
if ($sSetClass != $sLinkClass)
{
throw new CoreUnexpectedValue("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)");
}
$realvalue = clone $this->m_aCurrValues[$sAttCode];
$realvalue->UpdateFromCompleteList($value);
}
$realvalue = $oAttDef->MakeRealValue($value, $this);
$this->m_aCurrValues[$sAttCode] = $realvalue;
$this->m_aTouchedAtt[$sAttCode] = true;
unset($this->m_aModifiedAtt[$sAttCode]);
else
{
$realvalue = $oAttDef->MakeRealValue($value, $this);
}
$this->_Set($sAttCode, $realvalue);
foreach (MetaModel::ListMetaAttributes(get_class($this), $sAttCode) as $sMetaAttCode => $oMetaAttDef)
{
$this->Set($sMetaAttCode, $oMetaAttDef->MapValue($this));
$this->_Set($sMetaAttCode, $oMetaAttDef->MapValue($this));
}
// The object has changed, reset caches
@@ -538,7 +520,7 @@ abstract class DBObject implements iDisplay
else
{
// Not loaded... is it related to an external key?
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
if ($oAttDef->IsExternalField())
{
// Let's get the object and compute all of the corresponding attributes
// (i.e not only the requested attribute)
@@ -560,7 +542,7 @@ abstract class DBObject implements iDisplay
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sExtKeyAttCode))
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sExtKeyAttCode))
{
if ($oRemote)
{
@@ -568,7 +550,7 @@ abstract class DBObject implements iDisplay
}
else
{
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
}
$this->m_aLoadedAtt[$sCode] = true;
}
@@ -578,7 +560,7 @@ abstract class DBObject implements iDisplay
$value = $this->m_aCurrValues[$sAttCode];
}
if ($value instanceof DBObjectSet)
if ($value instanceof ormLinkSet)
{
$value->Rewind();
}
@@ -594,6 +576,19 @@ abstract class DBObject implements iDisplay
return $this->m_aOrigValues[$sAttCode];
}
/**
* Returns the default value of the $sAttCode. By default, returns the default value of the AttributeDefinition.
* Overridable.
*
* @param $sAttCode
* @return mixed
*/
public function GetDefaultValue($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
return $oAttDef->GetDefaultValue($this);
}
/**
* Returns data loaded by the mean of a dynamic and explicit JOIN
*/
@@ -738,7 +733,9 @@ abstract class DBObject implements iDisplay
else
{
$sHtmlLabel = htmlentities($this->Get($sAttCode.'_friendlyname'), ENT_QUOTES, 'UTF-8');
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel);
$bArchived = $this->IsArchived($sAttCode);
$bObsolete = $this->IsObsolete($sAttCode);
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel, null, true, $bArchived, $bObsolete);
}
}
@@ -817,10 +814,12 @@ abstract class DBObject implements iDisplay
* @param string $sHtmlLabel Label with HTML entities escaped (< escaped as &lt;)
* @param null $sUrlMakerClass
* @param bool|true $bWithNavigationContext
* @param bool|false $bArchived
* @param bool|false $bObsolete
* @return string
* @throws DictExceptionMissingString
*/
public static function MakeHyperLink($sObjClass, $sObjKey, $sHtmlLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
public static function MakeHyperLink($sObjClass, $sObjKey, $sHtmlLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true, $bArchived = false, $bObsolete = false)
{
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
@@ -843,19 +842,58 @@ abstract class DBObject implements iDisplay
}
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
$sUrl = ApplicationContext::MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass, $bWithNavigationContext);
if (strlen($sUrl) > 0)
$bClickable = !$bArchived || utils::IsArchiveMode();
if ($bArchived)
{
return "<a href=\"$sUrl\" title=\"$sHint\">$sHtmlLabel</a>";
$sSpanClass = 'archived';
$sFA = 'fa-archive object-archived';
$sHint = Dict::S('ObjectRef:Archived');
}
elseif ($bObsolete)
{
$sSpanClass = 'obsolete';
$sFA = 'fa-eye-slash object-obsolete';
$sHint = Dict::S('ObjectRef:Obsolete');
}
else
{
return $sHtmlLabel;
$sSpanClass = '';
$sFA = '';
}
if ($sFA == '')
{
$sIcon = '';
}
else
{
if ($bClickable)
{
$sIcon = "<span class=\"object-ref-icon fa $sFA fa-1x fa-fw\"></span>";
}
else
{
$sIcon = "<span class=\"object-ref-icon-disabled fa $sFA fa-1x fa-fw\"></span>";
}
}
if ($bClickable && (strlen($sUrl) > 0))
{
$sHLink = "<a class=\"object-ref-link\" href=\"$sUrl\">$sIcon$sHtmlLabel</a>";
}
else
{
$sHLink = $sIcon.$sHtmlLabel;
}
$sRet = "<span class=\"object-ref $sSpanClass\" title=\"$sHint\">$sHLink</span>";
return $sRet;
}
public function GetHyperlink($sUrlMakerClass = null, $bWithNavigationContext = true)
{
return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext);
$bArchived = $this->IsArchived();
$bObsolete = $this->IsObsolete();
return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext, $bArchived, $bObsolete);
}
public static function ComputeStandardUIPage($sClass)
@@ -1056,6 +1094,67 @@ abstract class DBObject implements iDisplay
return $iFlags | $iSynchroFlags; // Combine both sets of flags
}
/**
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
* for the given attribute in a transition
* @param $sAttCode string $sAttCode The code of the attribute
* @param $sStimulus string The stimulus code to apply
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
* @param $sOriginState string The state from which to apply $sStimulus, if empty current state will be used
* @return integer Flags: the binary combination of the flags applicable to this attribute
*/
public function GetTransitionFlags($sAttCode, $sStimulus, &$aReasons = array(), $sOriginState = '')
{
$iFlags = 0; // By default (if no lifecycle) no flag at all
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
// If no state attribute, there is no lifecycle
if (empty($sStateAttCode))
{
return $iFlags;
}
// Retrieving current state if necessary
if ($sOriginState === '')
{
$sOriginState = $this->Get($sStateAttCode);
}
// Retrieving attribute flags
$iAttributeFlags = $this->GetAttributeFlags($sAttCode, $aReasons, $sOriginState);
// Retrieving transition flags
$iTransitionFlags = MetaModel::GetTransitionFlags(get_class($this), $sOriginState, $sStimulus, $sAttCode);
// Merging transition flags with attribute flags
$iFlags = $iTransitionFlags | $iAttributeFlags;
return $iFlags;
}
/**
* Returns an array of attribute codes (with their flags) when $sStimulus is applied on the object in the $sOriginState state.
* Note: Attributes (and flags) from the target state and the transition are combined.
*
* @param $sStimulus string
* @param $sOriginState string Default is current state
* @return array
*/
public function GetTransitionAttributes($sStimulus, $sOriginState = null)
{
$sObjClass = get_class($this);
// Defining current state as origin state if not specified
if($sOriginState === null)
{
$sOriginState = $this->GetState();
}
$aAttributes = MetaModel::GetTransitionAttributes($sObjClass, $sStimulus, $sOriginState);
return $aAttributes;
}
/**
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
* for the given attribute for the current state of the object considered as an INITIAL state
@@ -1167,7 +1266,7 @@ abstract class DBObject implements iDisplay
$aChanges = $this->ListChanges();
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
foreach($aChanges as $sAttCode => $value)
{
$res = $this->CheckValue($sAttCode);
if ($res !== true)
@@ -1407,42 +1506,23 @@ abstract class DBObject implements iDisplay
return true;
}
// used only by insert
protected function OnObjectKeyReady()
{
// Meant to be overloaded
}
// used both by insert/update
private function DBWriteLinks()
{
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if (!$oAttDef->IsLinkSet()) continue;
if (!array_key_exists($sAttCode, $this->m_aTouchedAtt)) continue;
if (array_key_exists($sAttCode, $this->m_aModifiedAtt) && ($this->m_aModifiedAtt[$sAttCode] == false)) continue;
// Note: any change to this algorithm must be reproduced into the implementation of AttributeLinkSet::Equals()
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$sAdditionalKey = null;
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
{
$sAdditionalKey = $oAttDef->GetExtKeyToRemote();
}
$oComparator = new DBObjectSetComparator($this->m_aOrigValues[$sAttCode], $this->Get($sAttCode), array($sExtKeyToMe), $sAdditionalKey);
$aChanges = $oComparator->GetDifferences();
foreach($aChanges['added'] as $oLink)
{
// Make sure that the objects in the set point to "this"
$oLink->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey);
$id = $oLink->DBWrite();
}
foreach($aChanges['modified'] as $oLink)
{
// Objects in the set either remain attached or have been detached -> leave the link as is
$oLink->DBWrite();
}
foreach($aChanges['removed'] as $oLink)
{
$oLink->DBDelete();
}
$oLinkSet = $this->m_aCurrValues[$sAttCode];
$oLinkSet->DBWrite($this);
}
}
@@ -1644,11 +1724,21 @@ abstract class DBObject implements iDisplay
$this->DBInsertSingleTable($sParentClass);
}
$this->DBWriteLinks();
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->m_bIsInDB = true;
$this->m_bDirty = false;
foreach ($this->m_aCurrValues as $sAttCode => $value)
{
if (is_object($value))
{
$value = clone $value;
}
$this->m_aOrigValues[$sAttCode] = $value;
}
$this->AfterInsert();
@@ -1873,7 +1963,7 @@ abstract class DBObject implements iDisplay
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
if ($oAttDef->IsDirectField())
if ($oAttDef->IsBasedOnDBColumns())
{
$aDBChanges[$sAttCode] = $aChanges[$sAttCode];
}
@@ -1931,6 +2021,7 @@ abstract class DBObject implements iDisplay
{
$oFilter = new DBObjectSearch(get_class($this));
$oFilter->AddCondition('id', $this->m_iKey, '=');
$oFilter->AllowAllData();
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
CMDBSource::Query($sSQL);
@@ -2303,8 +2394,7 @@ abstract class DBObject implements iDisplay
*/
public function Reset($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$this->Set($sAttCode, $oAttDef->GetDefaultValue($this));
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
return true;
}
@@ -2840,7 +2930,7 @@ abstract class DBObject implements iDisplay
$oSearch->AllowAllData();
}
$oSet = new CMDBObjectSet($oSearch);
if ($oSet->Count() > 0)
if ($oSet->Count(1) > 0)
{
$aDependentObjects[$sRemoteClass][$sExtKeyAttCode] = array(
'attribute' => $oExtKeyAttDef,
@@ -3308,8 +3398,7 @@ abstract class DBObject implements iDisplay
{
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
break;
case 'nullify':
@@ -3393,7 +3482,7 @@ abstract class DBObject implements iDisplay
throw new Exception('Missing argument #1: source attribute');
}
$sSourceKeyAttCode = $aParams[0];
if (!MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
if (($sSourceKeyAttCode != 'id') && !MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
{
throw new Exception("Unknown attribute ".get_class($oObjectToRead)."::".$sSourceKeyAttCode);
}
@@ -3470,5 +3559,94 @@ abstract class DBObject implements iDisplay
throw new Exception("Invalid verb");
}
}
public function IsArchived($sKeyAttCode = null)
{
$bRet = false;
$sFlagAttCode = is_null($sKeyAttCode) ? 'archive_flag' : $sKeyAttCode.'_archive_flag';
if (MetaModel::IsValidAttCode(get_class($this), $sFlagAttCode) && $this->Get($sFlagAttCode))
{
$bRet = true;
}
return $bRet;
}
public function IsObsolete($sKeyAttCode = null)
{
$bRet = false;
$sFlagAttCode = is_null($sKeyAttCode) ? 'obsolescence_flag' : $sKeyAttCode.'_obsolescence_flag';
if (MetaModel::IsValidAttCode(get_class($this), $sFlagAttCode) && $this->Get($sFlagAttCode))
{
$bRet = true;
}
return $bRet;
}
/**
* @param $bArchive
* @throws Exception
*/
protected function DBWriteArchiveFlag($bArchive)
{
if (!MetaModel::IsArchivable(get_class($this)))
{
throw new Exception(get_class($this).' is not an archivable class');
}
$iFlag = $bArchive ? 1 : 0;
$sDate = $bArchive ? '"'.date(AttributeDate::GetSQLFormat()).'"' : 'null';
$sClass = get_class($this);
$sArchiveRoot = MetaModel::GetAttributeOrigin($sClass, 'archive_flag');
$sRootTable = MetaModel::DBGetTable($sArchiveRoot);
$sRootKey = MetaModel::DBGetKey($sArchiveRoot);
$aJoins = array("`$sRootTable`");
$aUpdates = array();
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
{
if (!MetaModel::IsValidAttCode($sParentClass, 'archive_flag')) continue;
$sTable = MetaModel::DBGetTable($sParentClass);
$aUpdates[] = "`$sTable`.`archive_flag` = $iFlag";
if ($sParentClass == $sArchiveRoot)
{
if (!$bArchive || $this->Get('archive_date') == '')
{
// Erase or set the date (do not change it)
$aUpdates[] = "`$sTable`.`archive_date` = $sDate";
}
}
else
{
$sKey = MetaModel::DBGetKey($sParentClass);
$aJoins[] = "`$sTable` ON `$sTable`.`$sKey` = `$sRootTable`.`$sRootKey`";
}
}
$sJoins = implode(' INNER JOIN ', $aJoins);
$sValues = implode(', ', $aUpdates);
$sUpdateQuery = "UPDATE $sJoins SET $sValues WHERE `$sRootTable`.`$sRootKey` = ".$this->GetKey();
CMDBSource::Query($sUpdateQuery);
}
/**
* Can be called to repair the database (tables consistency)
* The archive_date will be preserved
* @throws Exception
*/
public function DBArchive()
{
$this->DBWriteArchiveFlag(true);
$this->m_aCurrValues['archive_flag'] = true;
$this->m_aOrigValues['archive_flag'] = true;
}
public function DBUnarchive()
{
$this->DBWriteArchiveFlag(false);
$this->m_aCurrValues['archive_flag'] = false;
$this->m_aOrigValues['archive_flag'] = false;
$this->m_aCurrValues['archive_date'] = null;
$this->m_aOrigValues['archive_date'] = null;
}
}

63
core/dbobjectiterator.php Normal file
View File

@@ -0,0 +1,63 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* A set of persistent objects, could be heterogeneous as long as the objects in the set have a common ancestor class
*
* @package iTopORM
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface iDBObjectSetIterator extends Countable
{
/**
* The class of the objects of the collection (at least a common ancestor)
*
* @return string
*/
public function GetClass();
/**
* The total number of objects in the collection
*
* @return int
*/
public function Count();
/**
* Reset the cursor to the first item in the collection. Equivalent to Seek(0)
*
* @return DBObject The fetched object or null when at the end
*/
public function Rewind();
/**
* Position the cursor to the given 0-based position
*
* @param int $iRow
*/
public function Seek($iPosition);
/**
* Fetch the object at the current position in the collection and move the cursor to the next position.
*
* @return DBObject The fetched object or null when at the end
*/
public function Fetch();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -16,11 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
require_once('dbobjectiterator.php');
/**
* Object set management
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -30,31 +31,66 @@
*
* @package iTopORM
*/
class DBObjectSet
class DBObjectSet implements iDBObjectSetIterator
{
/**
* @var array
*/
protected $m_aAddedIds; // Ids of objects added (discrete lists)
/**
* @var hash array of (row => array of (classalias) => object/null) storing the objects added "in memory"
*/
protected $m_aAddedObjects;
/**
* @var array
*/
protected $m_aArgs;
/**
* @var array
*/
protected $m_aAttToLoad;
/**
* @var array
*/
protected $m_aOrderBy;
/**
* @var bool True when the filter has been used OR the set is built step by step (AddObject...)
*/
public $m_bLoaded;
/**
* @var int Total number of rows for the query without LIMIT. null if unknown yet
*/
protected $m_iNumTotalDBRows;
/**
* @var int Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
*/
protected $m_iNumLoadedDBRows;
/**
* @var int
*/
protected $m_iCurrRow;
/**
* @var DBSearch
*/
protected $m_oFilter;
/**
* @var mysqli_result
*/
protected $m_oSQLResult;
protected $m_bSort;
/**
* Create a new set based on a Search definition.
*
*
* @param DBSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param hash $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param array $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param hash $aExtendedDataSpec
* @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count)
* @param int $iLimitStart Index of the first row to load (i.e. equivalent to MySQL's LIMIT start, count)
* @param bool $bSort if false no order by is done
*/
public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0)
public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bSort = true)
{
$this->m_oFilter = $oFilter->DeepClone();
$this->m_aAddedIds = array();
@@ -64,11 +100,12 @@ class DBObjectSet
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
$this->m_iLimitCount = $iLimitCount;
$this->m_iLimitStart = $iLimitStart;
$this->m_bSort = $bSort;
$this->m_iNumTotalDBRows = null; // Total number of rows for the query without LIMIT. null if unknown yet
$this->m_iNumLoadedDBRows = 0; // Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
$this->m_bLoaded = false; // true when the filter has been used OR the set is built step by step (AddObject...)
$this->m_aAddedObjects = array(); // array of (row => array of (classalias) => object/null) storing the objects added "in memory"
$this->m_iNumTotalDBRows = null;
$this->m_iNumLoadedDBRows = 0;
$this->m_bLoaded = false;
$this->m_aAddedObjects = array();
$this->m_iCurrRow = 0;
$this->m_oSQLResult = null;
}
@@ -123,6 +160,17 @@ class DBObjectSet
$this->m_iCurrRow = 0;
$this->m_oSQLResult = null;
}
public function SetShowObsoleteData($bShow)
{
$this->m_oFilter->SetShowObsoleteData($bShow);
}
public function GetShowObsoleteData()
{
return $this->m_oFilter->GetShowObsoleteData();
}
/**
* Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
*
@@ -150,11 +198,25 @@ class DBObjectSet
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
if ($oAttDef->IsExternalKey())
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
// Add the external key friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
if (MetaModel::IsArchivable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_archive_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_archive_flag'] = $oArchiveFlagAttDef;
}
if (MetaModel::IsObsoletable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_obsolescence_flag'] = $oObsoleteFlagAttDef;
}
}
}
}
@@ -162,6 +224,20 @@ class DBObjectSet
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
if (MetaModel::IsArchivable($sClass))
{
// Add the archive flag if necessary
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, 'archive_flag');
$aAttToLoadWithAttDef[$sClassAlias]['archive_flag'] = $oArchiveFlagAttDef;
}
if (MetaModel::IsObsoletable($sClass))
{
// Add the obsolescence flag if necessary
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, 'obsolescence_flag');
$aAttToLoadWithAttDef[$sClassAlias]['obsolescence_flag'] = $oObsoleteFlagAttDef;
}
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttToLoadWithAttDef[$sClassAlias]))
{
@@ -192,7 +268,7 @@ class DBObjectSet
*
* @param string $sClass The class (or an ancestor) for the objects to be added in this set
*
* @return DBObject The empty set
* @return DBObjectSet The empty set
*/
static public function FromScratch($sClass)
{
@@ -264,8 +340,14 @@ class DBObjectSet
}
return self::FromArray($sTargetClass, $aTargets);
}
}
/**
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
*
* @param bool $bWithId
* @return array
*/
public function ToArray($bWithId = true)
{
$aRet = array();
@@ -332,8 +414,15 @@ class DBObjectSet
$iRow++;
}
return $aRet;
}
}
/**
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
*
* @param string $sAttCode
* @param bool $bWithId
* @return array
*/
public function GetColumnAsArray($sAttCode, $bWithId = true)
{
$aRet = array();
@@ -364,6 +453,7 @@ class DBObjectSet
{
// Make sure that we carry on the parameters of the set with the filter
$oFilter = $this->m_oFilter->DeepClone();
$oFilter->SetShowObsoleteData(true);
// Note: the arguments found within a set can be object (but not in a filter)
// That's why PrepareQueryArguments must be invoked there
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), $this->m_aArgs));
@@ -445,8 +535,8 @@ class DBObjectSet
/**
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
*
* @param hash $aOrderBy Format: field_code => boolean (true = ascending, false = descending)
*
* @param hash $aOrderBy Format: [alias.]attcode => boolean (true = ascending, false = descending)
*/
public function SetOrderBy($aOrderBy)
{
@@ -461,6 +551,34 @@ class DBObjectSet
}
}
/**
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
*
* @param hash $aAliases Format: alias => boolean (true = ascending, false = descending). If omitted, then it defaults to all the selected classes
*/
public function SetOrderByClasses($aAliases = null)
{
if ($aAliases === null)
{
$aAliases = array();
foreach ($this->GetSelectedClasses() as $sAlias => $sClass)
{
$aAliases[$sAlias] = true;
}
}
$aAttributes = array();
foreach ($aAliases as $sAlias => $bClassDirection)
{
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
{
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
}
}
$this->SetOrderBy($aAttributes);
}
/**
* Returns the 'count' limit for loading the rows from the DB
*
@@ -486,10 +604,15 @@ class DBObjectSet
*
* Limitation: the sort order has no effect on objects added in-memory
*
* @return hash Format: field_code => boolean (true = ascending, false = descending)
* @return array Format: field_code => boolean (true = ascending, false = descending)
*/
public function GetRealSortOrder()
{
if (!$this->m_bSort)
{
// No order by
return array();
}
// Get the class default sort order if not specified with the API
//
if (empty($this->m_aOrderBy))
@@ -511,14 +634,7 @@ class DBObjectSet
// Note: it is mandatory to set this value now, to protect against reentrance
$this->m_bLoaded = true;
if ($this->m_iLimitCount > 0)
{
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
}
else
{
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
}
$sSQL = $this->_makeSelectQuery($this->m_aAttToLoad);
if (is_object($this->m_oSQLResult))
{
@@ -527,8 +643,36 @@ class DBObjectSet
$this->m_oSQLResult = null;
}
$this->m_iNumTotalDBRows = null;
$this->m_oSQLResult = CMDBSource::Query($sSQL);
try
{
$this->m_oSQLResult = CMDBSource::Query($sSQL);
} catch (MySQLException $e)
{
// 1116 = ER_TOO_MANY_TABLES
// https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_too_many_tables
if ($e->getCode() != 1116)
{
throw $e;
}
// N.689 Workaround for the 61 max joins in MySQL : full lazy load !
$aAttToLoad = array();
foreach($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
{
$aAttToLoad[$sClassAlias] = array();
$bIsAbstractClass = MetaModel::IsAbstract($sClass);
$bIsClassWithChildren = MetaModel::HasChildrenClasses($sClass);
if ($bIsAbstractClass || $bIsClassWithChildren)
{
// we need finalClass field at least to be able to instantiate the real corresponding object !
$aAttToLoad[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
}
}
$sSQL = $this->_makeSelectQuery($aAttToLoad);
$this->m_oSQLResult = CMDBSource::Query($sSQL); // may fail again
}
if ($this->m_oSQLResult === false) return;
if (($this->m_iLimitCount == 0) && ($this->m_iLimitStart == 0))
@@ -539,24 +683,54 @@ class DBObjectSet
}
/**
* The total number of rows in this set. Independently of the SetLimit used for loading the set and taking into account the rows added in-memory.
*
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a SetLimit
*
* @return int The total number of rows for this set.
* @param string[] $aAttToLoad
*
* @return string SQL query
*/
public function Count()
private function _makeSelectQuery($aAttToLoad)
{
if ($this->m_iLimitCount > 0)
{
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $aAttToLoad,
$this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
}
else
{
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $aAttToLoad,
$this->m_aExtendedDataSpec);
}
return $sSQL;
}
/**
* The total number of rows in this set. Independently of the SetLimit used for loading the set and taking into
* account the rows added in-memory.
*
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a
* SetLimit
*
* @param int $iLimit used for autocomplete: the count is only used to know if the number of entries exceed
* a certain amount or not
*
* @return int The total number of rows for this set.
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
*/
public function Count($iLimit = 0)
{
if (is_null($this->m_iNumTotalDBRows))
{
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
$this->m_iNumTotalDBRows = $aRow['COUNT'];
}
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
}
@@ -849,22 +1023,6 @@ class DBObjectSet
return $oComparator->SetsAreEquivalent();
}
protected function GetObjectAt($iIndex)
{
if (!$this->m_bLoaded) $this->Load();
// Save the current position for iteration
$iCurrPos = $this->m_iCurrRow;
$this->Seek($iIndex);
$oObject = $this->Fetch();
// Restore the current position for iteration
$this->Seek($this->m_iCurrRow);
return $oObject;
}
/**
* Build a new set (in memory) made of objects of the given set which are NOT present in the current set
*
@@ -1126,19 +1284,27 @@ class DBObjectSetComparator
protected $aIDs1;
protected $aIDs2;
protected $aExcludedColumns;
/**
* @var iDBObjectSetIterator
*/
protected $oSet1;
/**
* @var iDBObjectSetIterator
*/
protected $oSet2;
protected $sAdditionalKeyColumn;
protected $aAdditionalKeys;
/**
* Initializes the comparator
* @param DBObjectSet $oSet1 The first set of objects to compare, or null
* @param DBObjectSet $oSet2 The second set of objects to compare, or null
* @param iDBObjectSetIterator $oSet1 The first set of objects to compare, or null
* @param iDBObjectSetIterator $oSet2 The second set of objects to compare, or null
* @param array $aExcludedColumns The list of columns (= attribute codes) to exclude from the comparison
* @param string $sAdditionalKeyColumn The attribute code of an additional column to be considered as a key indentifying the object (useful for n:n links)
*/
public function __construct($oSet1, $oSet2, $aExcludedColumns = array(), $sAdditionalKeyColumn = null)
public function __construct(iDBObjectSetIterator $oSet1, iDBObjectSetIterator $oSet2, $aExcludedColumns = array(), $sAdditionalKeyColumn = null)
{
$this->aFingerprints1 = null;
$this->aFingerprints2 = null;
@@ -1164,9 +1330,6 @@ class DBObjectSetComparator
if ($this->oSet1 !== null)
{
$aAliases = $this->oSet1->GetSelectedClasses();
if (count($aAliases) > 1) throw new Exception('DBObjectSetComparator does not support Sets with more than one column. $oSet1: ('.print_r($aAliases, true).')');
$this->oSet1->Rewind();
while($oObj = $this->oSet1->Fetch())
{
@@ -1182,9 +1345,6 @@ class DBObjectSetComparator
if ($this->oSet2 !== null)
{
$aAliases = $this->oSet2->GetSelectedClasses();
if (count($aAliases) > 1) throw new Exception('DBObjectSetComparator does not support Sets with more than one column. $oSet2: ('.print_r($aAliases, true).')');
$this->oSet2->Rewind();
while($oObj = $this->oSet2->Fetch())
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015-2016 Combodo SARL
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* A union of DBObjectSearches
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @copyright Copyright (C) 2015-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -55,6 +55,40 @@ class DBUnionSearch extends DBSearch
$this->ComputeSelectedClasses();
}
public function AllowAllData()
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AllowAllData();
}
}
public function IsAllDataAllowed()
{
foreach ($this->aSearches as $oSearch)
{
if ($oSearch->IsAllDataAllowed() === false) return false;
}
return true;
}
public function SetArchiveMode($bEnable)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->SetArchiveMode($bEnable);
}
parent::SetArchiveMode($bEnable);
}
public function SetShowObsoleteData($bShow)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->SetShowObsoleteData($bShow);
}
parent::SetShowObsoleteData($bShow);
}
/**
* Find the lowest common ancestor for each of the selected class
*/
@@ -187,6 +221,23 @@ class DBUnionSearch extends DBSearch
$this->ComputeSelectedClasses();
}
/**
* Change any alias of the query tree
*
* @param $sOldName
* @param $sNewName
* @return bool True if the alias has been found and changed
*/
public function RenameAlias($sOldName, $sNewName)
{
$bRet = false;
foreach ($this->aSearches as $oSearch)
{
$bRet = $oSearch->RenameAlias($sOldName, $sNewName) || $bRet;
}
return $bRet;
}
public function IsAny()
{
$bIsAny = true;
@@ -282,19 +333,33 @@ class DBUnionSearch extends DBSearch
}
}
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
/**
* @param DBObjectSearch $oFilter
* @param $sExtKeyAttCode
* @param int $iOperatorCode
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
* @throws CoreException
* @throws CoreWarning
*/
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
}
}
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
/**
* @param DBObjectSearch $oFilter
* @param $sForeignExtKeyAttCode
* @param int $iOperatorCode
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
*/
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode);
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
}
}
@@ -357,12 +422,12 @@ class DBUnionSearch extends DBSearch
/**
* Overloads for query building
*/
public function ToOQL($bDevelopParams = false, $aContextParams = null)
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
{
$aSubQueries = array();
foreach ($this->aSearches as $oSearch)
{
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams);
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams, $bWithAllowAllFlag);
}
$sRet = implode(' UNION ', $aSubQueries);
return $sRet;
@@ -409,11 +474,11 @@ class DBUnionSearch extends DBSearch
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
}
protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
{
if (count($this->aSearches) == 1)
{
return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
}
$aSQLQueries = array();
@@ -431,19 +496,30 @@ class DBUnionSearch extends DBSearch
$aSearchSelectedClasses[$sSearchAlias] = $this->aSelectedClasses[$sAlias];
}
if (is_null($aAttToLoad))
if ($bGetCount)
{
$aQueryAttToLoad = null;
// Select only ids for the count to allow optimization of joins
foreach($aSearchAliases as $sSearchAlias)
{
$aQueryAttToLoad[$sSearchAlias] = array();
}
}
else
{
// (Eventually) Transform the aliases
$aQueryAttToLoad = array();
foreach ($aAttToLoad as $sAlias => $aAttributes)
if (is_null($aAttToLoad))
{
$iColumn = array_search($sAlias, $aAliases);
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
$aQueryAttToLoad = null;
}
else
{
// (Eventually) Transform the aliases
$aQueryAttToLoad = array();
foreach($aAttToLoad as $sAlias => $aAttributes)
{
$iColumn = array_search($sAlias, $aAliases);
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
}
}
}
@@ -468,7 +544,13 @@ class DBUnionSearch extends DBSearch
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
}
}
$oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $aQueryGroupByExpr, $aSearchSelectedClasses);
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
if (count($aSearchAliases) > 1)
{
// Necessary to make sure that selected columns will match throughout all the queries
// (default order of selected fields depending on the order of JOINS)
$oSubQuery->SortSelectedFields();
}
$aSQLQueries[] = $oSubQuery;
}

View File

@@ -192,7 +192,7 @@ class DesignElement extends \DOMElement
* Returns the node directly under the given node
* @param $sTagName
* @param bool|true $bMustExist
* @return null
* @return MFElement
* @throws DOMFormatException
*/
public function GetUniqueElement($sTagName, $bMustExist = true)
@@ -216,7 +216,7 @@ class DesignElement extends \DOMElement
/**
* Returns the node directly under the current node, or null if missing
* @param $sTagName
* @return null
* @return MFElement
* @throws DOMFormatException
*/
public function GetOptionalElement($sTagName)

View File

@@ -195,8 +195,6 @@ class EMail
$aFailedRecipients = array();
$this->m_oMessage->setMaxLineLength(0);
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->getMaxLineLength());
IssueLog::Info(__METHOD__.' '.$this->m_oMessage->toString());
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
if ($iSent === 0)
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -264,8 +264,8 @@ class EventIssue extends Event
}
else
{
// Not a string
$aPost[$sKey] = (string) $sValue;
// Not a string (avoid warnings in case the value cannot be easily casted into a string)
$aPost[$sKey] = @(string) $sValue;
}
}
$this->Set('arguments_post', $aPost);
@@ -408,4 +408,30 @@ class EventLoginUsage extends Event
}
}
?>
class EventOnObject extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("obj_class", array("allowed_values"=>null, "sql"=>"obj_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("obj_key", array("allowed_values"=>null, "sql"=>"obj_key", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for a list
}
}

View File

@@ -188,10 +188,10 @@ EOF
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormCustomFieldsValue)
else if ($value instanceOf ormDocument)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, "\n", '', $oObj);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else
{

View File

@@ -0,0 +1,110 @@
<?php
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
class ExpressionCache
{
static private $aCache = array();
static public function GetCachedExpression($sClass, $sAttCode)
{
// read current cache
@include_once (static::GetCacheFileName());
$oExpr = null;
$sKey = static::GetKey($sClass, $sAttCode);
if (array_key_exists($sKey, static::$aCache))
{
$oExpr = static::$aCache[$sKey];
}
else
{
if (class_exists('ExpressionCacheData'))
{
if (array_key_exists($sKey, ExpressionCacheData::$aCache))
{
$sVal = ExpressionCacheData::$aCache[$sKey];
$oExpr = unserialize($sVal);
static::$aCache[$sKey] = $oExpr;
}
}
}
return $oExpr;
}
static public function Warmup()
{
$sFilePath = static::GetCacheFileName();
if (!is_file($sFilePath))
{
$content = <<<EOF
<?php
// Copyright (c) 2010-2017 Combodo SARL
// Generated Expression Cache file
class ExpressionCacheData
{
static \$aCache = array(
EOF;
foreach(MetaModel::GetClasses() as $sClass)
{
$content .= static::GetSerializedExpression($sClass, 'friendlyname');
if (MetaModel::IsObsoletable($sClass))
{
$content .= static::GetSerializedExpression($sClass, 'obsolescence_flag');
}
}
$content .= <<<EOF
);
}
EOF;
file_put_contents($sFilePath, $content);
}
}
static private function GetSerializedExpression($sClass, $sAttCode)
{
$sKey = static::GetKey($sClass, $sAttCode);
$oExpr = DBObjectSearch::GetPolymorphicExpression($sClass, $sAttCode);
return "'".$sKey."' => '".serialize($oExpr)."',\n";
}
/**
* @param $sClass
* @param $sAttCode
* @return string
*/
static private function GetKey($sClass, $sAttCode)
{
return $sClass.'::'.$sAttCode;
}
public static function GetCacheFileName()
{
return utils::GetCachePath().'expressioncache.php';
}
}

View File

@@ -1,4 +1,20 @@
<?php
// Copyright (C) 2016-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Base class for all possible implementations of HTML Sanitization
*/
@@ -138,8 +154,9 @@ class HTMLDOMSanitizer extends HTMLSanitizer
protected static $aTagsWhiteList = array(
'html' => array(),
'body' => array(),
'a' => array('href', 'name', 'style'),
'a' => array('href', 'name', 'style', 'target', 'title'),
'p' => array('style'),
'blockquote' => array('style'),
'br' => array(),
'span' => array('style'),
'div' => array('style'),
@@ -148,7 +165,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'u' => array(),
'em' => array(),
'strong' => array(),
'img' => array('src','style'),
'img' => array('src', 'style', 'alt', 'title'),
'ul' => array('style'),
'ol' => array('style'),
'li' => array('style'),
@@ -159,7 +176,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'nav' => array('style'),
'section' => array('style'),
'code' => array('style'),
'table' => array('style', 'width'),
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
'thead' => array('style'),
'tbody' => array('style'),
'tr' => array('style'),
@@ -183,21 +200,45 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'hr' => array('style'),
'pre' => array(),
'center' => array(),
'caption' => array(),
);
protected static $aAttrsWhiteList = array(
'href' => '/^(http:|https:)/i',
'src' => '/^(http:|https:|data:)/i',
);
protected static $aStylesWhiteList = array(
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height',
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height', 'text-align',
);
public function __construct()
{
// Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value.
// eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world"
if (!array_key_exists('href', self::$aAttrsWhiteList))
{
// Regular urls
$sUrlPattern = utils::GetConfig()->Get('url_validation_pattern');
// Mailto urls
$sMailtoPattern = '(mailto:(' . utils::GetConfig()->Get('email_validation_pattern') . ')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)';
$sPattern = $sUrlPattern . '|' . $sMailtoPattern;
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
self::$aAttrsWhiteList['href'] = $sPattern;
}
}
public function DoSanitize($sHTML)
{
$this->oDoc = new DOMDocument();
$this->oDoc->preserveWhitespace = true;
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
// therefore we have to do the transformation upfront
$sHTML = preg_replace('@<o:p>\s*</o:p>@', '<br>', $sHTML);
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
$this->CleanNode($this->oDoc);

View File

@@ -464,7 +464,7 @@ EOF
oEditor.on( 'instanceReady', function() {
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .editor_magnifier').length == 0)
{
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url($sAppRootUrl/images/full-screen.png)">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url(\\'$sAppRootUrl/images/full-screen.png\\')">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox .editor_magnifier').on('click', function() {
oEditor.execCommand('maximize');
if ($(this).closest('.cke_maximized').length != 0)
@@ -473,12 +473,15 @@ EOF
}
});
}
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
var oData = JSON.parse(upload.xhr.responseText);
this.replaceWith( '<img src="' + upload.url + '" ' +
'width="' + oData.width + '" ' +
'height="' + oData.height + '">' );
}
if (oEditor.widgets.registered.uploadimage)
{
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
var oData = JSON.parse(upload.xhr.responseText);
this.replaceWith( '<img src="' + upload.url + '" ' +
'width="' + oData.width + '" ' +
'height="' + oData.height + '">' );
}
}
});
});
EOF

View File

@@ -0,0 +1,93 @@
<?php
// Copyright (C) 2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Usage:
* require_once(...'introspection.class.inc.php');
*/
require_once('attributedef.class.inc.php');
class Introspection
{
protected $aAttributeHierarchy = array(); // class => child classes
protected $aAttributes = array();
public function __construct()
{
$this->InitAttributes();
}
protected function InitAttributes()
{
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
if ($sPHPClass == 'AttributeDefinition' || $oRefClass->isSubclassOf('AttributeDefinition'))
{
if ($oParentClass = $oRefClass->getParentClass())
{
$sParentClass = $oParentClass->getName();
if (!array_key_exists($sParentClass, $this->aAttributeHierarchy))
{
$this->aAttributeHierarchy[$sParentClass] = array();
}
$this->aAttributeHierarchy[$sParentClass][] = $sPHPClass;
}
else
{
$sParentClass = null;
}
$this->aAttributes[$sPHPClass] = array(
'parent' => $sParentClass,
'LoadInObject' => $sPHPClass::LoadInObject(),
'LoadFromDB' => $sPHPClass::LoadFromDB(),
'IsBasedOnDBColumns' => $sPHPClass::IsBasedOnDBColumns(),
'IsBasedOnOQLExpression' => $sPHPClass::IsBasedOnOQLExpression(),
'IsExternalField' => $sPHPClass::IsExternalField(),
'IsScalar' => $sPHPClass::IsScalar(),
'IsLinkset' => $sPHPClass::IsLinkset(),
'IsHierarchicalKey' => $sPHPClass::IsHierarchicalKey(),
);
}
}
}
public function GetAttributes()
{
return $this->aAttributes;
}
public function GetAttributeHierarchy()
{
return $this->aAttributeHierarchy;
}
public function EnumAttributeCharacteristics()
{
return array(
'LoadInObject' => 'Is the value stored in the object itself?',
'LoadFromDB' => 'Is the value read from the DB?',
'IsBasedOnDBColumns' => 'Is this a value stored within one or several columns?',
'IsBasedOnOQLExpression' => 'Is this a value computed after other attributes, by the mean of an OQL expression?',
'IsExternalField' => 'Is this a value stored on a related object (external key)?',
'IsScalar' => 'Is this a value that makes sense in a SQL/OQL expression?',
'IsLinkset' => 'Is this a collection (1-N or N-N)?',
'IsHierarchicalKey' => 'Is this attribute an external key pointing to the host class?',
);
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* File logging
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -69,81 +69,54 @@ class FileLog
}
}
class SetupLog
abstract class LogAPI
{
protected static $m_oFileLog;
public static function Enable($sTargetFile)
{
self::$m_oFileLog = new FileLog($sTargetFile);
static::$m_oFileLog = new FileLog($sTargetFile);
}
public static function Error($sText)
{
self::$m_oFileLog->Error($sText);
if (static::$m_oFileLog)
{
static::$m_oFileLog->Error($sText);
}
}
public static function Warning($sText)
{
self::$m_oFileLog->Warning($sText);
if (static::$m_oFileLog)
{
static::$m_oFileLog->Warning($sText);
}
}
public static function Info($sText)
{
self::$m_oFileLog->Info($sText);
if (static::$m_oFileLog)
{
static::$m_oFileLog->Info($sText);
}
}
public static function Ok($sText)
{
self::$m_oFileLog->Ok($sText);
if (static::$m_oFileLog)
{
static::$m_oFileLog->Ok($sText);
}
}
}
class IssueLog
class SetupLog extends LogAPI
{
protected static $m_oFileLog;
public static function Enable($sTargetFile)
{
self::$m_oFileLog = new FileLog($sTargetFile);
}
public static function Error($sText)
{
self::$m_oFileLog->Error($sText);
}
public static function Warning($sText)
{
self::$m_oFileLog->Warning($sText);
}
public static function Info($sText)
{
self::$m_oFileLog->Info($sText);
}
public static function Ok($sText)
{
self::$m_oFileLog->Ok($sText);
}
protected static $m_oFileLog = null;
}
class ToolsLog
class IssueLog extends LogAPI
{
protected static $m_oFileLog;
public static function Enable($sTargetFile)
{
self::$m_oFileLog = new FileLog($sTargetFile);
}
public static function Error($sText)
{
self::$m_oFileLog->Error($sText);
}
public static function Warning($sText)
{
self::$m_oFileLog->Warning($sText);
}
public static function Info($sText)
{
self::$m_oFileLog->Info($sText);
}
public static function Ok($sText)
{
self::$m_oFileLog->Ok($sText);
}
protected static $m_oFileLog = null;
}
class ToolsLog extends LogAPI
{
protected static $m_oFileLog = null;
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -89,8 +89,7 @@ class ModuleDesign extends \Combodo\iTop\DesignDocument
}
else
{
var_dump($aFiles);
$aAvailable = array();
$aAvailable = array();
foreach ($aFiles as $sFile)
{
$aAvailable[] = "'".basename($sFile, '.xml')."'";

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2013-2016 Combodo SARL
// Copyright (C) 2013-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -24,7 +24,7 @@
* Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP.
*
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @copyright Copyright (C) 2013-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class iTopMutex
@@ -38,10 +38,14 @@ class iTopMutex
{
// Compute the name of a lock for mysql
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
$oConfig = MetaModel::GetConfig();
if ($oConfig === null)
{
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
}
$sDBName = $oConfig->GetDBName();
$sDBSubname = $oConfig->GetDBSubname();
$this->sName = 'itop.'.$sName;
$this->sName = $sName;
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
{
// If the name supplied already ends with the expected suffix
@@ -49,7 +53,10 @@ class iTopMutex
// running cron job by its mutex, without knowing if the config already exists or not
$this->sName .= $sDBName.$sDBSubname;
}
// Limit the length of the name for MySQL > 5.7.5
$this->sName = 'itop.'.md5($this->sName);
$this->bLocked = false; // Not yet locked
if (!array_key_exists($this->sName, self::$aAcquiredLocks))

View File

@@ -1,9 +1,9 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -15,14 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* General definition of an expression tree (could be OQL, SQL or whatever)
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//
class MissingQueryArgument extends CoreException
{
@@ -45,6 +38,13 @@ abstract class Expression
// recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
abstract public function Render(&$aArgs = null, $bRetrofitParams = false);
/**
* Recursively browse the expression tree
* @param Closure $callback
* @return mixed
*/
abstract public function Browse(Closure $callback);
abstract public function ApplyParameters($aArgs);
// recursively builds an array of class => fieldname
@@ -76,11 +76,21 @@ abstract class Expression
return self::FromOQL(base64_decode($sValue));
}
/**
* @param $sConditionExpr
* @return Expression
*/
static public function FromOQL($sConditionExpr)
{
static $aCache = array();
if (array_key_exists($sConditionExpr, $aCache))
{
return unserialize($aCache[$sConditionExpr]);
}
$oOql = new OqlInterpreter($sConditionExpr);
$oExpression = $oOql->ParseExpression();
$aCache[$sConditionExpr] = serialize($oExpression);
return $oExpression;
}
@@ -90,14 +100,22 @@ abstract class Expression
return $oSql;
}
public function LogAnd($oExpr)
/**
* @param Expression $oExpr
* @return Expression
*/
public function LogAnd(Expression $oExpr)
{
if ($this->IsTrue()) return clone $oExpr;
if ($oExpr->IsTrue()) return clone $this;
return new BinaryExpression($this, 'AND', $oExpr);
}
public function LogOr($oExpr)
/**
* @param Expression $oExpr
* @return Expression
*/
public function LogOr(Expression $oExpr)
{
return new BinaryExpression($this, 'OR', $oExpr);
}
@@ -139,6 +157,11 @@ class SQLExpression extends Expression
return $this->m_sSQL;
}
public function Browse(Closure $callback)
{
$callback($this);
}
public function ApplyParameters($aArgs)
{
}
@@ -203,6 +226,10 @@ class BinaryExpression extends Expression
{
throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr)));
}
if ( (($sOperator == "IN") || ($sOperator == "NOT IN")) && !$oRightExpr instanceof ListExpression)
{
throw new CoreException("Expecting a List Expression object on the right hand for operator $sOperator", array('found_class' => get_class($oRightExpr)));
}
$this->m_oLeftExpr = $oLeftExpr;
$this->m_oRightExpr = $oRightExpr;
$this->m_sOperator = $sOperator;
@@ -245,7 +272,14 @@ class BinaryExpression extends Expression
$sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
return "($sLeft $sOperator $sRight)";
}
public function Browse(Closure $callback)
{
$callback($this);
$this->m_oLeftExpr->Browse($callback);
$this->m_oRightExpr->Browse($callback);
}
public function ApplyParameters($aArgs)
{
if ($this->m_oLeftExpr instanceof VariableExpression)
@@ -358,7 +392,7 @@ class UnaryExpression extends Expression
public function GetValue()
{
return $this->m_value;
}
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
@@ -366,6 +400,11 @@ class UnaryExpression extends Expression
return CMDBSource::Quote($this->m_value);
}
public function Browse(Closure $callback)
{
$callback($this);
}
public function ApplyParameters($aArgs)
{
}
@@ -484,6 +523,12 @@ class FieldExpression extends UnaryExpression
public function GetParent() {return $this->m_sParent;}
public function GetName() {return $this->m_sName;}
public function SetParent($sParent)
{
$this->m_sParent = $sParent;
$this->m_value = $sParent.'.'.$this->m_sName;
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -579,7 +624,7 @@ class FieldExpression extends UnaryExpression
$iObjKey = (int)$sValue;
if ($iObjKey > 0)
{
$oObject = MetaModel::GetObject($sObjClass, $iObjKey);
$oObject = MetaModel::GetObjectWithArchive($sObjClass, $iObjKey);
$sRes = $oObject->GetHyperlink();
}
else
@@ -680,7 +725,7 @@ class VariableExpression extends UnaryExpression
throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>array_keys($aArgs)));
}
}
public function RenameParam($sOldName, $sNewName)
{
if ($this->m_sName == $sOldName)
@@ -768,6 +813,15 @@ class ListExpression extends Expression
return '('.implode(', ', $aRes).')';
}
public function Browse(Closure $callback)
{
$callback($this);
foreach ($this->m_aExpressions as $oExpr)
{
$oExpr->Browse($callback);
}
}
public function ApplyParameters($aArgs)
{
$aRes = array();
@@ -888,6 +942,15 @@ class FunctionExpression extends Expression
return $this->m_sVerb.'('.implode(', ', $aRes).')';
}
public function Browse(Closure $callback)
{
$callback($this);
foreach ($this->m_aArgs as $iPos => $oExpr)
{
$oExpr->Browse($callback);
}
}
public function ApplyParameters($aArgs)
{
$aRes = array();
@@ -1071,6 +1134,12 @@ class IntervalExpression extends Expression
return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
}
public function Browse(Closure $callback)
{
$callback($this);
$this->m_oValue->Browse($callback);
}
public function ApplyParameters($aArgs)
{
if ($this->m_oValue instanceof VariableExpression)
@@ -1151,6 +1220,15 @@ class CharConcatExpression extends Expression
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
}
public function Browse(Closure $callback)
{
$callback($this);
foreach ($this->m_aExpressions as $oExpr)
{
$oExpr->Browse($callback);
}
}
public function ApplyParameters($aArgs)
{
$aRes = array();
@@ -1255,6 +1333,15 @@ class CharConcatWSExpression extends CharConcatExpression
return "CAST(CONCAT_WS($sSep, ".implode(', ', $aRes).") AS CHAR)";
}
public function Browse(Closure $callback)
{
$callback($this);
foreach ($this->m_aExpressions as $oExpr)
{
$oExpr->Browse($callback);
}
}
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
$aRes = array();
@@ -1269,15 +1356,41 @@ class CharConcatWSExpression extends CharConcatExpression
class QueryBuilderExpressions
{
/**
* @var Expression
*/
protected $m_oConditionExpr;
/**
* @var Expression[]
*/
protected $m_aSelectExpr;
/**
* @var Expression[]
*/
protected $m_aGroupByExpr;
/**
* @var Expression[]
*/
protected $m_aJoinFields;
/**
* @var string[]
*/
protected $m_aClassIds;
public function __construct($oSearch, $aGroupByExpr = null)
public function __construct(DBObjectSearch $oSearch, $aGroupByExpr = null)
{
$this->m_oConditionExpr = $oSearch->GetCriteria();
if (!$oSearch->GetShowObsoleteData())
{
foreach ($oSearch->GetSelectedClasses() as $sAlias => $sClass)
{
if (MetaModel::IsObsoletable($sClass))
{
$oNotObsolete = new BinaryExpression(new FieldExpression('obsolescence_flag', $sAlias), '=', new ScalarExpression(0));
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oNotObsolete);
}
}
}
$this->m_aSelectExpr = array();
$this->m_aGroupByExpr = $aGroupByExpr;
$this->m_aJoinFields = array();
@@ -1304,23 +1417,35 @@ class QueryBuilderExpressions
return $this->m_oConditionExpr;
}
/**
* @return Expression|mixed
*/
public function PopJoinField()
{
return array_pop($this->m_aJoinFields);
}
public function AddSelect($sAttAlias, $oExpression)
/**
* @param string $sAttAlias
* @param Expression $oExpression
*/
public function AddSelect($sAttAlias, Expression $oExpression)
{
$this->m_aSelectExpr[$sAttAlias] = $oExpression;
}
//$oConditionTree = $oConditionTree->LogAnd($oFinalClassRestriction);
public function AddCondition($oExpression)
/**
* @param Expression $oExpression
*/
public function AddCondition(Expression $oExpression)
{
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oExpression);
}
public function PushJoinField($oExpression)
/**
* @param Expression $oExpression
*/
public function PushJoinField(Expression $oExpression)
{
array_push($this->m_aJoinFields, $oExpression);
}
@@ -1403,6 +1528,4 @@ class QueryBuilderExpressions
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);
}
}
}
?>
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Wrapper to execute the parser, lexical analyzer and normalization of an OQL query
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -83,6 +83,9 @@ class OqlInterpreter
return $res;
}
/**
* @return OqlQuery
*/
public function ParseQuery()
{
$oRes = $this->Parse();
@@ -93,6 +96,9 @@ class OqlInterpreter
return $oRes;
}
/**
* @return Expression
*/
public function ParseExpression()
{
$oRes = $this->Parse();
@@ -103,5 +109,3 @@ class OqlInterpreter
return $oRes;
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormCaseLog {
@@ -191,8 +191,15 @@ class ormCaseLog {
public function __toString()
{
return $this->m_sLog;
if($this->IsEmpty()) return '';
return $this->m_sLog;
}
public function IsEmpty()
{
return ($this->m_sLog === null);
}
public function ClearModifiedFlag()
{
@@ -388,8 +395,9 @@ class ormCaseLog {
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
{
// Don't display the first element, that is still considered as editable
$iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
array_shift($aIndex);
$aLastEntry = end($aIndex);
$iPos = $aLastEntry['separator_length'] + $aLastEntry['text_length'];
array_pop($aIndex);
}
for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
{
@@ -519,57 +527,36 @@ class ormCaseLog {
if ($this->m_bModified)
{
$aLatestEntry = end($this->m_aIndex);
if ($aLatestEntry['user_name'] != $sOnBehalfOf)
if ($aLatestEntry['user_name'] == $sOnBehalfOf)
{
$bMergeEntries = false;
}
else
{
$bMergeEntries = true;
// Append the new text to the previous one
$sPreviousText = substr($this->m_sLog, $aLatestEntry['separator_length'], $aLatestEntry['text_length']);
$sText = $sPreviousText."\n".$sText;
// Cleanup the previous entry
array_pop($this->m_aIndex);
$this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length'] + $aLatestEntry['text_length']);
}
}
if ($bMergeEntries)
{
$aLatestEntry = end($this->m_aIndex);
$this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length']);
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText."\n");
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $aLatestEntry['text_length'] + $iTextlength,
'separator_length' => $iSepLength,
'format' => 'html',
);
}
else
{
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => 'html',
);
}
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => 'html',
);
$this->m_bModified = true;
}
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
if (isset($oJson->user_id))
{
if (!UserRights::IsAdministrator())
@@ -616,10 +603,16 @@ class ormCaseLog {
}
else
{
// TODO: what is the default format ? text ?
// The default is HTML
$sFormat = 'html';
}
$sText = isset($oJson->message) ? $oJson->message : '';
if ($sFormat == 'html')
{
$sText = HTMLSanitizer::Sanitize($sText);
}
$sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
@@ -639,12 +632,12 @@ class ormCaseLog {
}
public function GetModifiedEntry()
public function GetModifiedEntry($sFormat = 'text')
{
$sModifiedEntry = '';
if ($this->m_bModified)
{
$sModifiedEntry = $this->GetLatestEntry();
$sModifiedEntry = $this->GetLatestEntry($sFormat);
}
return $sModifiedEntry;
}

View File

@@ -87,7 +87,7 @@ class ormCustomFieldsValue
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
$oHandler = $oAttDef->GetHandler($this->GetValues());
return 'template...verb='.$sVerb.' sur "'.json_encode($this->aCurrentValues).'"';
return $oHandler->GetForTemplate($this->aCurrentValues, $sVerb, $bLocalize);
}
/**

View File

@@ -51,6 +51,8 @@ class ormDocument
public function __toString()
{
if($this->IsEmpty()) return '';
return MyHelpers::beautifulstr($this->m_data, 100, true);
}
@@ -115,21 +117,29 @@ class ormDocument
*/
public function GetDownloadLink($sClass, $Id, $sAttCode)
{
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
return "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8')."</a>\n";
}
/**
* Returns an URL to display a document like an image
* @return string
*/
public function GetDisplayURL($sClass, $Id, $sAttCode)
{
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode";
}
/**
* Returns an URL to download a document like an image (uses HTTP caching)
* @return string
*/
*/
public function GetDownloadURL($sClass, $Id, $sAttCode)
{
// Compute a signature to reset the cache anytime the data changes (this is acceptable if used only with icon files)
$sSignature = md5($this->GetData());
return utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
return utils::GetAbsoluteUrlAppRoot() . "pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
}
public function IsPreviewAvailable()
{
$bRet = false;
@@ -176,7 +186,7 @@ class ormDocument
{
$oPage->TrashUnexpectedOutput();
$oPage->SetContentType($oDocument->GetMimeType());
//$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
$oPage->add($oDocument->GetData());
}
}

View File

@@ -0,0 +1,729 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
require_once('dbobjectiterator.php');
/**
* The value for an attribute representing a set of links between the host object and "remote" objects
*
* @package iTopORM
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
{
protected $sHostClass; // subclass of DBObject
protected $sAttCode; // xxxxxx_list
protected $sClass; // class of the links
/**
* @var DBObjectSet
*/
protected $oOriginalSet;
/**
* @var DBObject[] array of iObjectId => DBObject
*/
protected $aOriginalObjects = null;
/**
* @var bool
*/
protected $bHasDelta = false;
/**
* Object from the original set, minus the removed objects
* @var DBObject[] array of iObjectId => DBObject
*/
protected $aPreserved = array();
/**
* @var DBObject[] New items
*/
protected $aAdded = array();
/**
* @var DBObject[] Modified items (could also be found in aPreserved)
*/
protected $aModified = array();
/**
* @var int[] Removed items
*/
protected $aRemoved = array();
/**
* @var int Position in the collection
*/
protected $iCursor = 0;
/**
* __toString magical function overload.
*/
public function __toString()
{
return '';
}
/**
* ormLinkSet constructor.
* @param $sHostClass
* @param $sAttCode
* @param DBObjectSet|null $oOriginalSet
* @throws Exception
*/
public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
{
$this->sHostClass = $sHostClass;
$this->sAttCode = $sAttCode;
$this->oOriginalSet = $oOriginalSet ? clone $oOriginalSet : null;
$oAttDef = MetaModel::GetAttributeDef($sHostClass, $sAttCode);
if (!$oAttDef instanceof AttributeLinkedSet)
{
throw new Exception("ormLinkSet: $sAttCode is not a link set");
}
$this->sClass = $oAttDef->GetLinkedClass();
if ($oOriginalSet && ($oOriginalSet->GetClass() != $this->sClass))
{
throw new Exception("ormLinkSet: wrong class for the original set, found {$oOriginalSet->GetClass()} while expecting {$oAttDef->GetLinkedClass()}");
}
}
public function GetFilter()
{
return clone $this->oOriginalSet->GetFilter();
}
/**
* Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
*
* @param hash $aAttToLoad Format: alias => array of attribute_codes
*
* @return void
*/
public function OptimizeColumnLoad($aAttToLoad)
{
$this->oOriginalSet->OptimizeColumnLoad($aAttToLoad);
}
/**
* @param DBObject $oLink
*/
public function AddItem(DBObject $oLink)
{
assert($oLink instanceof $this->sClass);
// No impact on the iteration algorithm
$iObjectId = $oLink->GetKey();
$this->aAdded[$iObjectId] = $oLink;
$this->bHasDelta = true;
}
/**
* @param DBObject $oObject
* @param string $sClassAlias
* @deprecated Since iTop 2.4, use ormLinkset->AddItem() instead.
*/
public function AddObject(DBObject $oObject, $sClassAlias = '')
{
$this->AddItem($oObject);
}
/**
* @param $iObjectId
*/
public function RemoveItem($iObjectId)
{
if (array_key_exists($iObjectId, $this->aPreserved))
{
unset($this->aPreserved[$iObjectId]);
$this->aRemoved[$iObjectId] = $iObjectId;
$this->bHasDelta = true;
}
else
{
if (array_key_exists($iObjectId, $this->aAdded))
{
unset($this->aAdded[$iObjectId]);
}
}
}
/**
* @param DBObject $oLink
*/
public function ModifyItem(DBObject $oLink)
{
assert($oLink instanceof $this->sClass);
$iObjectId = $oLink->GetKey();
if (array_key_exists($iObjectId, $this->aPreserved))
{
unset($this->aPreserved[$iObjectId]);
$this->aModified[$iObjectId] = $oLink;
$this->bHasDelta = true;
}
}
protected function LoadOriginalIds()
{
if ($this->aOriginalObjects === null)
{
if ($this->oOriginalSet)
{
$this->aOriginalObjects = $this->GetArrayOfIndex();
$this->aPreserved = $this->aOriginalObjects; // Copy (not effective until aPreserved gets modified)
foreach ($this->aRemoved as $iObjectId)
{
if (array_key_exists($iObjectId, $this->aPreserved))
{
unset($this->aPreserved[$iObjectId]);
}
}
foreach ($this->aModified as $iObjectId => $oLink)
{
if (array_key_exists($iObjectId, $this->aPreserved))
{
unset($this->aPreserved[$iObjectId]);
}
}
}
else
{
// Nothing to load
$this->aOriginalObjects = array();
$this->aPreserved = array();
}
}
}
/**
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
* @return array
*/
protected function GetArrayOfIndex()
{
$aRet = array();
$this->oOriginalSet->Rewind();
$iRow = 0;
while ($oObject = $this->oOriginalSet->Fetch())
{
$aRet[$oObject->GetKey()] = $iRow++;
}
return $aRet;
}
/**
* @param bool $bWithId
* @return array
* @deprecated Since iTop 2.4, use foreach($this as $oItem){} instead
*/
public function ToArray($bWithId = true)
{
$aRet = array();
foreach($this as $oItem)
{
if ($bWithId)
{
$aRet[$oItem->GetKey()] = $oItem;
}
else
{
$aRet[] = $oItem;
}
}
return $aRet;
}
/**
* @param string $sAttCode
* @param bool $bWithId
* @return array
*/
public function GetColumnAsArray($sAttCode, $bWithId = true)
{
$aRet = array();
foreach($this as $oItem)
{
if ($bWithId)
{
$aRet[$oItem->GetKey()] = $oItem->Get($sAttCode);
}
else
{
$aRet[] = $oItem->Get($sAttCode);
}
}
return $aRet;
}
/**
* The class of the objects of the collection (at least a common ancestor)
*
* @return string
*/
public function GetClass()
{
return $this->sClass;
}
/**
* The total number of objects in the collection
*
* @return int
*/
public function Count()
{
$this->LoadOriginalIds();
$iRet = count($this->aPreserved) + count($this->aAdded) + count($this->aModified);
return $iRet;
}
/**
* Position the cursor to the given 0-based position
*
* @param $iPosition
* @throws Exception
* @internal param int $iRow
*/
public function Seek($iPosition)
{
$this->LoadOriginalIds();
$iCount = $this->Count();
if ($iPosition >= $iCount)
{
throw new Exception("Invalid position $iPosition: the link set is made of $iCount items.");
}
$this->rewind();
for($iPos = 0 ; $iPos < $iPosition ; $iPos++)
{
$this->next();
}
}
/**
* Fetch the object at the current position in the collection and move the cursor to the next position.
*
* @return DBObject|null The fetched object or null when at the end
*/
public function Fetch()
{
$this->LoadOriginalIds();
$ret = $this->current();
if ($ret === false)
{
$ret = null;
}
$this->next();
return $ret;
}
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
*/
public function current()
{
$this->LoadOriginalIds();
$iPreservedCount = count($this->aPreserved);
if ($this->iCursor < $iPreservedCount)
{
$iRet = current($this->aPreserved);
$this->oOriginalSet->Seek($iRet);
$oRet = $this->oOriginalSet->Fetch();
}
else
{
$iModifiedCount = count($this->aModified);
if($this->iCursor < $iPreservedCount + $iModifiedCount)
{
$oRet = current($this->aModified);
}
else
{
$oRet = current($this->aAdded);
}
}
return $oRet;
}
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->LoadOriginalIds();
$iPreservedCount = count($this->aPreserved);
if ($this->iCursor < $iPreservedCount)
{
next($this->aPreserved);
}
else
{
$iModifiedCount = count($this->aModified);
if($this->iCursor < $iPreservedCount + $iModifiedCount)
{
next($this->aModified);
}
else
{
next($this->aAdded);
}
}
// Increment AFTER moving the internal cursors because when starting aModified / aAdded, we must leave it intact
$this->iCursor++;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->iCursor;
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
$this->LoadOriginalIds();
$iCount = $this->Count();
$bRet = ($this->iCursor < $iCount);
return $bRet;
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->LoadOriginalIds();
$this->iCursor = 0;
reset($this->aPreserved);
reset($this->aAdded);
reset($this->aModified);
}
public function HasDelta()
{
return $this->bHasDelta;
}
/**
* This method has been designed specifically for AttributeLinkedSet:Equals and as such it assumes that the passed argument is a clone of this.
* @param ormLinkSet $oFellow
* @return bool|null
* @throws Exception
*/
public function Equals(ormLinkSet $oFellow)
{
$bRet = null;
if ($this === $oFellow)
{
$bRet = true;
}
else
{
if ( ($this->oOriginalSet !== $oFellow->oOriginalSet)
&& ($this->oOriginalSet->GetFilter()->ToOQL() != $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
{
throw new Exception('ormLinkSet::Equals assumes that compared link sets have the same original scope');
}
if ($this->HasDelta())
{
throw new Exception('ormLinkSet::Equals assumes that left link set had no delta');
}
$bRet = !$oFellow->HasDelta();
}
return $bRet;
}
public function UpdateFromCompleteList(iDBObjectSetIterator $oFellow)
{
if ($oFellow === $this)
{
throw new Exception('ormLinkSet::UpdateFromCompleteList assumes that the passed link set is at least a clone of the current one');
}
$bUpdateFromDelta = false;
if ($oFellow instanceof ormLinkSet)
{
if ( ($this->oOriginalSet === $oFellow->oOriginalSet)
|| ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
{
$bUpdateFromDelta = true;
}
}
if ($bUpdateFromDelta)
{
// Same original set -> simply update the delta
$this->iCursor = 0;
$this->aAdded = $oFellow->aAdded;
$this->aRemoved = $oFellow->aRemoved;
$this->aModified = $oFellow->aModified;
$this->aPreserved = $oFellow->aPreserved;
$this->bHasDelta = $oFellow->bHasDelta;
}
else
{
// For backward compatibility reasons, let's rebuild a delta...
// Reset the delta
$this->iCursor = 0;
$this->aAdded = array();
$this->aRemoved = array();
$this->aModified = array();
$this->aPreserved = ($this->aOriginalObjects === null) ? array() : $this->aOriginalObjects;
$this->bHasDelta = false;
/** @var AttributeLinkedSet $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($this->sHostClass, $this->sAttCode);
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$sAdditionalKey = null;
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
{
$sAdditionalKey = $oAttDef->GetExtKeyToRemote();
}
// Compare both collections by iterating the whole sets, order them, a build a fingerprint based on meaningful data (what make the difference)
$oComparator = new DBObjectSetComparator($this, $oFellow, array($sExtKeyToMe), $sAdditionalKey);
$aChanges = $oComparator->GetDifferences();
foreach ($aChanges['added'] as $oLink)
{
$this->AddItem($oLink);
}
foreach ($aChanges['modified'] as $oLink)
{
$this->ModifyItem($oLink);
}
foreach ($aChanges['removed'] as $oLink)
{
$this->RemoveItem($oLink->GetKey());
}
}
}
/**
* @param DBObject $oHostObject
*/
public function DBWrite(DBObject $oHostObject)
{
/** @var AttributeLinkedSet $oAttDef */
$oAttDef = MetaModel::GetAttributeDef(get_class($oHostObject), $this->sAttCode);
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$sExtKeyToRemote = $oAttDef->IsIndirect() ? $oAttDef->GetExtKeyToRemote() : 'n/a';
$aCheckLinks = array();
$aCheckRemote = array();
foreach ($this->aAdded as $oLink)
{
if ($oLink->IsNew())
{
if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
{
//todo: faire un test qui passe dans cette branche !
$aCheckRemote[] = $oLink->Get($sExtKeyToRemote);
}
}
else
{
//todo: faire un test qui passe dans cette branche !
$aCheckLinks[] = $oLink->GetKey();
}
}
foreach ($this->aRemoved as $iLinkId)
{
$aCheckLinks[] = $iLinkId;
}
foreach ($this->aModified as $iLinkId => $oLink)
{
$aCheckLinks[] = $oLink->GetKey();
}
// Critical section : serialize any write access to these links
//
$oMtx = new iTopMutex('Write-'.$this->sClass);
$oMtx->Lock();
// Check for the existing links
//
/** @var DBObject[] $aExistingLinks */
$aExistingLinks = array();
/** @var Int[] $aExistingRemote */
$aExistingRemote = array();
if (count($aCheckLinks) > 0)
{
$oSearch = new DBObjectSearch($this->sClass);
$oSearch->AddCondition('id', $aCheckLinks, 'IN');
$oSet = new DBObjectSet($oSearch);
$aExistingLinks = $oSet->ToArray();
}
// Check for the existing remote objects
//
if (count($aCheckRemote) > 0)
{
$oSearch = new DBObjectSearch($this->sClass);
$oSearch->AddCondition($sExtKeyToMe, $oHostObject->GetKey(), '=');
$oSearch->AddCondition($sExtKeyToRemote, $aCheckRemote, 'IN');
$oSet = new DBObjectSet($oSearch);
$aExistingRemote = $oSet->GetColumnAsArray($sExtKeyToRemote, true);
}
// Write the links according to the existing links
//
foreach ($this->aAdded as $oLink)
{
// Make sure that the objects in the set point to "this"
$oLink->Set($sExtKeyToMe, $oHostObject->GetKey());
if ($oLink->IsNew())
{
if (count($aCheckRemote) > 0)
{
$bIsDuplicate = false;
foreach($aExistingRemote as $sLinkKey => $sExtKey)
{
if ($sExtKey == $oLink->Get($sExtKeyToRemote))
{
// Do not create a duplicate
// + In the case of a remove action followed by an add action
// of an existing link,
// the final state to consider is add action,
// so suppress the entry in the removed list.
if (array_key_exists($sLinkKey, $this->aRemoved))
{
unset($this->aRemoved[$sLinkKey]);
}
$bIsDuplicate = true;
break;
}
}
if ($bIsDuplicate)
{
continue;
}
}
}
else
{
if (!array_key_exists($oLink->GetKey(), $aExistingLinks))
{
$oLink->DBClone();
}
}
$oLink->DBWrite();
}
foreach ($this->aRemoved as $iLinkId)
{
if (array_key_exists($iLinkId, $aExistingLinks))
{
$oLink = $aExistingLinks[$iLinkId];
if ($oAttDef->IsIndirect())
{
$oLink->DBDelete();
}
else
{
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToMe);
if ($oExtKeyToRemote->IsNullAllowed())
{
if ($oLink->Get($sExtKeyToMe) == $oHostObject->GetKey())
{
// Detach the link object from this
$oLink->Set($sExtKeyToMe, 0);
$oLink->DBUpdate();
}
}
else
{
$oLink->DBDelete();
}
}
}
}
// Note: process modifications at the end: if a link to remove has also been listed as modified, then it will be gracefully ignored
foreach ($this->aModified as $iLinkId => $oLink)
{
if (array_key_exists($oLink->GetKey(), $aExistingLinks))
{
$oLink->DBUpdate();
}
else
{
$oLink->DBClone();
}
}
// End of the critical section
//
$oMtx->Unlock();
}
public function ToDBObjectSet($bShowObsolete = true)
{
$oAttDef = MetaModel::GetAttributeDef($this->sHostClass, $this->sAttCode);
$oLinkSearch = $this->GetFilter();
if ($oAttDef->IsIndirect())
{
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
$sTargetClass = $oLinkingAttDef->GetTargetClass();
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
{
$oNotObsolete = new BinaryExpression(
new FieldExpression('obsolescence_flag', $sTargetClass),
'=',
new ScalarExpression(0)
);
$oNotObsoleteRemote = new DBObjectSearch($sTargetClass);
$oNotObsoleteRemote->AddConditionExpression($oNotObsolete);
$oLinkSearch->AddCondition_PointingTo($oNotObsoleteRemote, $sExtKeyToRemote);
}
}
$oLinkSet = new DBObjectSet($oLinkSearch);
$oLinkSet->SetShowObsoleteData($bShowObsolete);
if ($this->HasDelta())
{
$oLinkSet->AddObjectArray($this->aAdded);
}
return $oLinkSet;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2014 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -22,7 +22,7 @@ require_once('backgroundprocess.inc.php');
* ormStopWatch
* encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -260,7 +260,7 @@ class ormStopWatch
return $iRet;
}
protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec)
protected function ComputeDeadline($oObject, $oAttDef, $iPercent, $iStartTime, $iDurationSec)
{
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
if ($sWorkingTimeComputer == '')
@@ -280,7 +280,7 @@ class ormStopWatch
}
// GetDeadline($oObject, $iDuration, DateTime $oStartDate)
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate);
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate, $iPercent);
$iRet = $oDeadline->format('U');
return $iRet;
}
@@ -384,8 +384,8 @@ class ormStopWatch
$sAttCode = $oAttDef->GetCode();
WorkingTimeRecorder::Start($oObject, $iComputationRefTime, "ormStopWatch-Deadline-$iPercent-$sAttCode", 'Core:ExplainWTC:StopWatch-Deadline', array("Class:$sClass/Attribute:$sAttCode", $iPercent));
}
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $iPercent, $this->iStarted, $iThresholdDuration);
if (class_exists('WorkingTimeRecorder'))
{
@@ -494,6 +494,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'";
$oFilter = DBObjectSearch::FromOQL($sExpression);
$oSet = new DBObjectSet($oFilter);
$oSet->OptimizeColumnLoad(array($sAttCode));
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
{
$sClass = get_class($oObj);

View File

@@ -226,9 +226,9 @@ EOF
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$sUrl = 'data:' . $value->GetMimeType() . ';base64,' . base64_encode($value->GetData());
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
}
$sRet = '<img src="' . $sUrl . '" style="width: ' . $iNewWidth . 'px; height: ' . $iNewHeight . 'px">';
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">' : '';
$sRet = '<div class="view-image">'.$sRet.'</div>';
}
else

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -30,6 +30,7 @@ class QueryBuilderContext
protected $m_aTableAliases;
protected $m_aModifierProperties;
protected $m_aSelectedClasses;
protected $m_aFilteredTables;
public $m_oQBExpressions;
@@ -40,6 +41,7 @@ class QueryBuilderContext
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
$this->m_aTableAliases = array();
$this->m_aFilteredTables = array();
$this->m_aModifierProperties = $aModifierProperties;
if (is_null($aSelectedClasses))
@@ -84,4 +86,21 @@ class QueryBuilderContext
{
return $this->m_aSelectedClasses[$sAlias];
}
public function AddFilteredTable($sTableAlias, $oCondition)
{
if (array_key_exists($sTableAlias, $this->m_aFilteredTables))
{
$this->m_aFilteredTables[$sTableAlias][] = $oCondition;
}
else
{
$this->m_aFilteredTables[$sTableAlias] = array($oCondition);
}
}
public function GetFilteredTables()
{
return $this->m_aFilteredTables;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* Data structures (i.e. PHP classes) to build and use relation graphs
*
* @copyright Copyright (C) 2015 Combodo SARL
* @copyright Copyright (C) 2015-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*
*/
@@ -106,7 +106,7 @@ class RelationRedundancyNode extends GraphNode
/**
* Make a normalized ID to ensure the uniqueness of such a node
*/
public static function MakeId($sRelCode, $sNeighbourId, $oSinkObject)
public static function MakeId($sRelCode, $sNeighbourId, $oSourceObject, $oSinkObject)
{
return 'redundancy-'.$sRelCode.'-'.$sNeighbourId.'-'.get_class($oSinkObject).'::'.$oSinkObject->GetKey();
}
@@ -209,7 +209,7 @@ class RelationGraph extends SimpleGraph
{
if ($sOQL === '') return;
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSearch = static::MakeSearch($sOQL);
$aAliases = $oSearch->GetSelectedClasses();
if (count($aAliases) < 2 )
{
@@ -326,7 +326,7 @@ class RelationGraph extends SimpleGraph
$this->AddRelatedObjects($sRelCode, false, $oSinkNode, $iMaxDepth, $bEnableRedundancy);
//echo "<h5>After processing of {$oSinkNode->GetId()}</h5>\n".$this->DumpAsHtmlImage()."<br/>\n";
}
// Mark also the "context" nodes as reached and record the "root causes" for each node
$oIterator = new RelationTypeIterator($this, 'Node');
foreach($oIterator as $oNode)
@@ -393,7 +393,7 @@ class RelationGraph extends SimpleGraph
$sQuery = $bDown ? $aQueryInfo['sQueryDown'] : $aQueryInfo['sQueryUp'];
try
{
$oFlt = DBObjectSearch::FromOQL($sQuery);
$oFlt = static::MakeSearch($sQuery);
$oObjSet = new DBObjectSet($oFlt, array(), $oObject->ToArgsForQuery());
$oRelatedObj = $oObjSet->Fetch();
}
@@ -407,11 +407,11 @@ class RelationGraph extends SimpleGraph
do
{
set_time_limit($iLoopTimeLimit);
$sObjectRef = RelationObjectNode::MakeId($oRelatedObj);
$oRelatedNode = $this->GetNode($sObjectRef);
if (is_null($oRelatedNode))
{
{
$oRelatedNode = new RelationObjectNode($this, $oRelatedObj);
}
$oSourceNode = $bDown ? $oObjectNode : $oRelatedNode;
@@ -449,17 +449,21 @@ class RelationGraph extends SimpleGraph
$oObject = $oToNode->GetProperty('object');
if ($this->IsRedundancyEnabled($sRelCode, $aQueryInfo, $oToNode))
{
$sId = RelationRedundancyNode::MakeId($sRelCode, $aQueryInfo['sNeighbour'], $oToNode->GetProperty('object'));
$sUniqueNeighbourId = $aQueryInfo['sDefinedInClass'].'-'.$aQueryInfo['sNeighbour'];
$sId = RelationRedundancyNode::MakeId($sRelCode, $sUniqueNeighbourId, $oFromNode->GetProperty('object'), $oToNode->GetProperty('object'));
$oRedundancyNode = $this->GetNode($sId);
if (is_null($oRedundancyNode))
{
// Get the upper neighbours
$sQuery = $aQueryInfo['sQueryUp'];
if (!$sQuery)
{
throw new Exception("Redundancy cannot be enabled on the relation $sRelCode/{$aQueryInfo['sDefinedInClass']}/{$aQueryInfo['sNeighbour']}: its direction is \"{$aQueryInfo['sDirection']}\"");
}
try
{
$oFlt = DBObjectSearch::FromOQL($sQuery);
$oFlt = static::MakeSearch($sQuery);
$oObjSet = new DBObjectSet($oFlt, array(), $oObject->ToArgsForQuery());
$iCount = $oObjSet->Count();
}
@@ -540,10 +544,13 @@ class RelationGraph extends SimpleGraph
{
if ($oAttDef->Get('relation_code') == $sRelCode)
{
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
if ($oAttDef->Get('from_class') == $aQueryInfo['sFromClass'])
{
$oRet = $oAttDef;
break;
if ($oAttDef->Get('neighbour_id') == $aQueryInfo['sNeighbour'])
{
$oRet = $oAttDef;
break;
}
}
}
}
@@ -574,4 +581,17 @@ class RelationGraph extends SimpleGraph
}
return $aResults;
}
protected static function MakeSearch($sOQL)
{
$oSearch = DBSearch::FromOQL($sOQL);
if (MetaModel::IsObsoletable($oSearch->GetClass()))
{
// Exclude obsolete objects anytime
$oSearch->AddCondition('obsolescence_flag', 0);
}
// Exclude archived objects anytime
$oSearch->SetArchiveMode(false);
return $oSearch;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* Data structures (i.e. PHP classes) to manage "graphs"
*
* @copyright Copyright (C) 2015 Combodo SARL
* @copyright Copyright (C) 2015-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*
* Example:
@@ -346,14 +346,15 @@ class SimpleGraph
}
unset($this->aNodes[$oNode->GetId()]);
}
/**
* Removes the given node but preserves the connectivity of the graph
* all "source" nodes are connected to all "sink" nodes
* @param GraphNode $oNode
* @param bool $bAllowLoopingEdge
* @throws SimpleGraphException
*/
public function FilterNode(GraphNode $oNode)
public function FilterNode(GraphNode $oNode, $bAllowLoopingEdge = false)
{
if (!array_key_exists($oNode->GetId(), $this->aNodes)) throw new SimpleGraphException('Cannot filter the node (id='.$oNode->GetId().') from the graph. The node was not found in the graph.');
@@ -362,13 +363,19 @@ class SimpleGraph
foreach($oNode->GetOutgoingEdges() as $oEdge)
{
$sSinkId = $oEdge->GetSinkNode()->GetId();
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
if ($sSinkId != $oNode->GetId())
{
$aSinkNodes[$sSinkId] = $oEdge->GetSinkNode();
}
$this->_RemoveEdge($oEdge);
}
foreach($oNode->GetIncomingEdges() as $oEdge)
{
$sSourceId = $oEdge->GetSourceNode()->GetId();
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
if ($sSourceId != $oNode->GetId())
{
$aSourceNodes[$sSourceId] = $oEdge->GetSourceNode();
}
$this->_RemoveEdge($oEdge);
}
unset($this->aNodes[$oNode->GetId()]);
@@ -377,7 +384,10 @@ class SimpleGraph
{
foreach($aSinkNodes as $sSinkId => $oSinkNode)
{
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
if ($bAllowLoopingEdge || ($oSourceNode->GetId() != $oSinkNode->GetId()))
{
$oEdge = new RelationEdge($this, $oSourceNode, $oSinkNode);
}
}
}
}

View File

@@ -31,6 +31,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tno_localize: (optional) pass 1 to retrieve the raw (untranslated) values for enumerated fields. Default: 0.");
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
$oP->p(" *\tformatted_text: set to 1 to formatted text fields with their HTML markup, 0 to remove formatting. Default is 1 (= formatted text)");
}
public function EnumFormParts()
@@ -51,12 +52,19 @@ class SpreadsheetBulkExport extends TabularBulkExport
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:SpreadsheetOptions').'</legend>');
$oP->add('<table>');
$oP->add('<tr>');
$oP->add('<td><input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label></td>');
$oP->add('<td style="vertical-align:top">');
$sChecked = (utils::ReadParam('formatted_text', 1) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="hidden" name="formatted_text" value="0">'); // Trick to pass the zero value if the checkbox below is unchecked, since we want the default value to be "1"
$oP->add('<input type="checkbox" id="spreadsheet_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="spreadsheet_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label><br/><br/>');
$oP->add('<input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label>');
$oP->add('</td>');
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
$oP->add('<td>');
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
@@ -65,23 +73,23 @@ class SpreadsheetBulkExport extends TabularBulkExport
$sFormatInput = '<input type="text" size="15" name="date_format" id="spreadsheet_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="spreadsheet_date_time_format_custom" name="spreadsheet_date_format_radio" value="custom"'.$sCustomChecked.'><label for="spreadsheet_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td>');
$oP->add('</tr>');
$oP->add('</table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
<<<EOF
$('#spreadsheet_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#form_part_spreadsheet_options').on('preview_updated', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
$('#spreadsheet_date_time_format_default').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
$('#spreadsheet_date_time_format_custom').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); });
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); FormatDatesInPreview('spreadsheet', 'spreadsheet'); }).on('keyup', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
EOF
);
);
break;
default:
return parent:: DisplayFormPart($oP, $sPartId);
}
@@ -90,26 +98,27 @@ EOF
public function ReadParameters()
{
parent::ReadParameters();
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 1, true);
$sDateFormatRadio = utils::ReadParam('spreadsheet_date_format_radio', '');
switch($sDateFormatRadio)
{
case 'default':
// Export from the UI => format = same as is the UI
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
break;
// Export from the UI => format = same as is the UI
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
break;
case 'custom':
// Custom format specified from the UI
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
break;
// Custom format specified from the UI
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
break;
default:
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
}
}
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode != 'id')
@@ -126,6 +135,7 @@ EOF
protected function GetValue($oObj, $sAttCode)
{
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
switch($sAttCode)
{
case 'id':
@@ -147,13 +157,26 @@ EOF
{
$sRet = '';
}
elseif ($oAttDef instanceof AttributeText)
{
if ($bFormattedText)
{
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
$sRet = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
}
else
{
$sRet = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
}
}
elseif ($oAttDef instanceof AttributeString)
{
$sRet = $oObj->GetAsHTML($sAttCode);
}
elseif ($oAttDef instanceof AttributeCustomFields)
{
$sRet = $oObj->GetAsHTML($sAttCode);
// Stick to the weird implementation made in GetNextChunk
$sRet = utils::TextToHtml($oObj->GetEditValue($sAttCode));
}
else
{
@@ -175,7 +198,7 @@ EOF
{
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
// Then the fix is to force the reset of header values Pragma and Cache-control
$oPage->add_header("Pragma:", true);
$oPage->add_header("Cache-control:", true);
}
@@ -230,13 +253,14 @@ EOF
$oSet = new DBObjectSet($this->oSearch);
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
$this->OptimizeColumnLoad($oSet);
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
$bFormattedText = (array_key_exists('formatted_text', $this->aStatusInfo) ? $this->aStatusInfo['formatted_text'] : false);
// Date & time formats
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
$oTimeFormat = new DateTimeFormat($oDateTimeFormat->ToTimeFormat());
$iCount = 0;
$sData = '';
$iPreviousTimeLimit = ini_get('max_execution_time');
@@ -258,55 +282,75 @@ EOF
$sData .= "<td x:str></td>";
continue;
}
switch($sAttCode)
{
case 'id':
$sField = $oObj->GetKey();
$sData .= "<td>$sField</td>";
break;
$sField = $oObj->GetKey();
$sData .= "<td>$sField</td>";
break;
default:
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime')
{
// Split the date and time in two columns
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
$sData .= "<td>$sDate</td>";
$sData .= "<td>$sTime</td>";
}
else if (get_class($oFinalAttDef) == 'AttributeDate')
{
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
$sData .= "<td>$sDate</td>";
}
else if($oAttDef instanceof AttributeCaseLog)
{
$rawValue = $oObj->Get($sAttCode);
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
// Trick for Excel: treat the content as text even if it begins with an equal sign
$sData .= "<td x:str>$sField</td>";
}
else if($oAttDef instanceof AttributeString)
{
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
$sData .= "<td x:str>$sField</td>";
}
else
{
$rawValue = $oObj->Get($sAttCode);
if ($this->bLocalizeOutput)
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime')
{
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
// Split the date and time in two columns
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
$sData .= "<td>$sDate</td>";
$sData .= "<td>$sTime</td>";
}
else if (get_class($oFinalAttDef) == 'AttributeDate')
{
$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
$sData .= "<td>$sDate</td>";
}
else if($oAttDef instanceof AttributeCaseLog)
{
$rawValue = $oObj->Get($sAttCode);
$sField = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
// Trick for Excel: treat the content as text even if it begins with an equal sign
$sData .= "<td x:str>$sField</td>";
}
elseif ($oAttDef instanceof AttributeText)
{
if ($bFormattedText)
{
// Replace paragraphs (<p...>...</p>, etc) by line breaks (<br/>) since Excel (pre-2016) splits the cells when there is a paragraph
$sField = static::HtmlToSpreadsheet($oObj->GetAsHTML($sAttCode));
}
else
{
// Convert to plain text
$sField = utils::HtmlToText($oObj->GetAsHTML($sAttCode));
}
$sData .= "<td x:str>$sField</td>";
}
elseif ($oAttDef instanceof AttributeCustomFields)
{
// GetAsHTML returns a table that would not fit
$sField = utils::TextToHtml($oObj->GetEditValue($sAttCode));
$sData .= "<td x:str>$sField</td>";
}
else if($oAttDef instanceof AttributeString)
{
$sField = $oObj->GetAsHTML($sAttCode, $this->bLocalizeOutput);
$sData .= "<td x:str>$sField</td>";
}
else
{
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
$rawValue = $oObj->Get($sAttCode);
if ($this->bLocalizeOutput)
{
$sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8');
}
else
{
$sField = htmlentities($rawValue, ENT_QUOTES, 'UTF-8');
}
$sData .= "<td>$sField</td>";
}
$sData .= "<td>$sField</td>";
}
}
}
@@ -354,4 +398,51 @@ EOF
{
return 'html';
}
/**
* Cleanup all markup displayed as line breaks (except <br> tags) since this
* causes Excel (pre-2016) to generate extra lines in the table, thus breaking
* the tabular disposition of the export
* Note: Excel 2016 also refuses line breaks, so the only solution for this case is alas plain text
* @param string $sHtml The HTML to cleanup
* @return string The cleaned HTML
*/
public static function HtmlToSpreadsheet($sHtml)
{
if (trim(strip_tags($sHtml)) === '')
{
// Display this value as an empty cell in the table
return '&nbsp;';
}
// The tags listed here are a subset of the whitelist defined in HTMLDOMSanitizer
// Tags causing a visual "line break" in the displayed page (i.e. display: block) are to be replaced by a <span> followed by a <br/>
// in order to preserve any inline style/attribute of the removed tag
$aTagsToReplace = array(
'pre', 'div', 'p', 'hr', 'center', 'h1', 'h2', 'h3', 'h4', 'li', 'fieldset', 'legend', 'nav', 'section', 'tr', 'caption',
);
// Tags to completely remove from the markup
$aTagsToRemove = array(
'table', 'thead', 'tbody', 'ul', 'ol', 'td', 'th',
);
// Remove the englobing <div class="HTML" >...</div> to prevent an extra line break
$sHtml = preg_replace('|^<div class="HTML" >(.*)</div>$|s', '$1', $sHtml); // Must use the "s" (. matches newline) modifier
foreach($aTagsToReplace as $sTag)
{
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '<span $1>', $sHtml);
$sHtml = preg_replace("|</{$sTag}>|i", '</span><br/>', $sHtml);
}
foreach($aTagsToRemove as $sTag)
{
$sHtml = preg_replace("|<{$sTag} ?([^>]*)>|is", '', $sHtml);
$sHtml = preg_replace("|</{$sTag}>|i", '', $sHtml);
}
// Remove any trailing <br/>, if any, to prevent an extra line break
$sHtml = preg_replace("|<br/>$|", '', $sHtml);
return $sHtml;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015-2016 Combodo SARL
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* SQLObjectQuery
* build a mySQL compatible SQL query
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @copyright Copyright (C) 2015-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -36,7 +36,8 @@
class SQLObjectQuery extends SQLQuery
{
private $m_SourceOQL = '';
public $m_aContextData = null;
public $m_iOriginalTableCount = 0;
private $m_sTable = '';
private $m_sTableAlias = '';
private $m_aFields = array();
@@ -46,7 +47,7 @@ class SQLObjectQuery extends SQLQuery
private $m_aValues = array(); // Values to set in case of an update query
private $m_oSelectedIdField = null;
private $m_aJoinSelects = array();
private $m_bBeautifulQuery = false;
protected $m_bBeautifulQuery = false;
// Data set by PrepareRendering()
private $__aFrom;
@@ -138,6 +139,11 @@ class SQLObjectQuery extends SQLQuery
$this->m_aFields = $aExpressions;
}
public function SortSelectedFields()
{
ksort($this->m_aFields);
}
public function AddSelect($sAlias, $oExpression)
{
$this->m_aFields[$sAlias] = $oExpression;
@@ -161,7 +167,7 @@ class SQLObjectQuery extends SQLQuery
}
else
{
$this->m_oConditionExpr->LogAnd($oConditionExpr);
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oConditionExpr);
}
}
@@ -306,6 +312,14 @@ class SQLObjectQuery extends SQLQuery
$this->PrepareRendering();
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
if ($bGetCount)
{
if (count($this->__aSelectedIdFields) > 0)
@@ -316,11 +330,13 @@ class SQLObjectQuery extends SQLQuery
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
}
$sCountFields = implode(', ', $aCountFields);
$sSQL = "SELECT$sLineSep COUNT(DISTINCT $sCountFields) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
// Count can be limited for performance reason, in this case the total amount is not important,
// we only need to know if the number of entries is greater than a certain amount.
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
}
else
{
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
}
}
else
@@ -331,14 +347,7 @@ class SQLObjectQuery extends SQLQuery
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
$sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy $sLimit";
}
return $sSQL;
@@ -505,6 +514,7 @@ class SQLObjectQuery extends SQLQuery
public function OptimizeJoins($aUsedTables, $bTopCall = true)
{
$this->m_iOriginalTableCount = $this->CountTables();
if ($bTopCall)
{
// Top call: complete the list of tables absolutely required to perform the right query
@@ -529,7 +539,18 @@ class SQLObjectQuery extends SQLQuery
return (count($this->m_aJoinSelects) == 0);
}
protected function CollectUsedTables(&$aTables)
public function CountTables()
{
$iRet = 1;
foreach ($this->m_aJoinSelects as $i => $aJoinInfo)
{
$oSQLQuery = $aJoinInfo["select"];
$iRet += $oSQLQuery->CountTables();
}
return $iRet;
}
public function CollectUsedTables(&$aTables)
{
$this->m_oConditionExpr->CollectUsedParents($aTables);
foreach($this->m_aFields as $sFieldAlias => $oField)
@@ -596,4 +617,5 @@ class SQLObjectQuery extends SQLQuery
// None of the tables is in the list of required tables
return $bResult;
}
}

View File

@@ -39,7 +39,7 @@ require_once('cmdbsource.class.inc.php');
abstract class SQLQuery
{
private $m_SourceOQL = '';
private $m_bBeautifulQuery = false;
protected $m_bBeautifulQuery = false;
public function __construct()
{

View File

@@ -58,7 +58,7 @@ class SQLUnionQuery extends SQLQuery
{
$aQueriesHtml[] = '<p>'.$oSQLQuery->DisplayHtml().'</p>';
}
echo implode('UNION', $aQueries);
echo implode('UNION', $aQueriesHtml);
}
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '')
@@ -85,7 +85,6 @@ class SQLUnionQuery extends SQLQuery
{
$this->m_bBeautifulQuery = $bBeautifulQuery;
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
$sIndent = $this->m_bBeautifulQuery ? " " : null;
$aSelects = array();
foreach ($this->aQueries as $oSQLQuery)
@@ -93,36 +92,33 @@ class SQLUnionQuery extends SQLQuery
// Render SELECTS without orderby/limit/count
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
}
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
if ($bGetCount)
if ($iLimitCount > 0)
{
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep";
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
if ($bGetCount)
{
$sSelects = '('.implode(" $sLimit)$sLineSep UNION$sLineSep(", $aSelects)." $sLimit)";
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep) AS _union_tatooine_";
}
else
{
$aSelects = array();
foreach ($this->aQueries as $oSQLQuery)
{
// Render SELECT without orderby/limit/count
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
}
$sSelect = $this->aQueries[0]->RenderSelectClause();
$sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy);
if (!empty($sOrderBy))
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
$sOrderBy = "ORDER BY $sOrderBy$sLineSep $sLimit";
$sSQL = '('.implode(")$sLineSep UNION$sLineSep (", $aSelects).')'.$sLineSep.$sOrderBy;
}
else
{
$sLimit = '';
$sSQL = '('.implode(" $sLimit)$sLineSep UNION$sLineSep (", $aSelects)." $sLimit)";
}
$sSQL = $sSelects.$sLineSep.$sOrderBy.' '.$sLimit;
}
return $sSQL;
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* User rights management API
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -161,6 +161,7 @@ abstract class UserRightsAddOnAPI
}
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
abstract class User extends cmdbAbstractObject
{
public static function Init()
@@ -245,7 +246,7 @@ abstract class User extends cmdbAbstractObject
{
if (is_null($this->oContactObject))
{
if ($this->Get('contactid') != 0)
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
{
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
}
@@ -337,6 +338,8 @@ abstract class User extends cmdbAbstractObject
'bulkread' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_READ),
'write' => $this->GetGrantAsHtml($sClass, UR_ACTION_MODIFY),
'bulkwrite' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_MODIFY),
'delete' => $this->GetGrantAsHtml($sClass, UR_ACTION_DELETE),
'bulkdelete' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_DELETE),
'stimuli' => $sStimuli,
);
}
@@ -349,6 +352,8 @@ abstract class User extends cmdbAbstractObject
$aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+'));
$aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+'));
$aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+'));
$aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+'));
$aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+'));
$aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+'));
$oPage->table($aDisplayConfig, $aDisplayData);
}
@@ -583,6 +588,13 @@ class UserRights
return false;
}
self::$m_oUser = $oUser;
if (isset($_SESSION['impersonate_user']))
{
self::$m_oRealUser = self::$m_oUser;
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
}
Dict::SetUserLanguage(self::GetUserLanguage());
return true;
}
@@ -641,6 +653,29 @@ class UserRights
}
}
/**
* Tells whether or not the archive mode is allowed to the current user
* @return boolean
*/
static function CanBrowseArchive()
{
if (is_null(self::$m_oUser))
{
$bRet = false;
}
elseif (isset($_SESSION['archive_allowed']))
{
$bRet = $_SESSION['archive_allowed'];
}
else
{
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
$_SESSION['archive_allowed'] = $bRet;
}
return $bRet;
}
public static function CanChangePassword()
{
if (MetaModel::DBIsReadOnly())
@@ -679,24 +714,50 @@ class UserRights
}
}
public static function Impersonate($sName, $sPassword)
/**
* @param string $sName Login identifier of the user to impersonate
* @return bool True if an impersonation occurred
*/
public static function Impersonate($sName)
{
if (!self::CheckLogin()) return false;
$bRet = false;
$oUser = self::FindUser($sName);
if (is_null($oUser))
if ($oUser)
{
return false;
}
if (!$oUser->CheckCredentials($sPassword))
{
return false;
$bRet = true;
if (is_null(self::$m_oRealUser))
{
// First impersonation
self::$m_oRealUser = self::$m_oUser;
}
if (self::$m_oRealUser && (self::$m_oRealUser->GetKey() == $oUser->GetKey()))
{
// Equivalent to "Deimpersonate"
self::Deimpersonate();
}
else
{
// Do impersonate!
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
$_SESSION['impersonate_user'] = $sName;
self::_ResetSessionCache();
}
}
return $bRet;
}
self::$m_oRealUser = self::$m_oUser;
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
return true;
public static function Deimpersonate()
{
if (!is_null(self::$m_oRealUser))
{
self::$m_oUser = self::$m_oRealUser;
Dict::SetUserLanguage(self::GetUserLanguage());
unset($_SESSION['impersonate_user']);
self::_ResetSessionCache();
}
}
public static function GetUser()
@@ -828,6 +889,11 @@ class UserRights
return self::$m_oRealUser->Get('login');
}
public static function GetRealUserObject()
{
return self::$m_oRealUser;
}
public static function GetRealUserId()
{
if (is_null(self::$m_oRealUser))
@@ -1045,7 +1111,12 @@ class UserRights
{
$oUser = self::$m_oUser;
}
if ($oUser->GetKey() == self::$m_oUser->GetKey())
if ($oUser === null)
{
// Not logged in: no profile at all
$aProfiles = array();
}
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
{
// Data about the current user can be found into the session data
if (array_key_exists('profile_list', $_SESSION))
@@ -1086,8 +1157,16 @@ class UserRights
self::$m_aAdmins = array();
self::$m_aPortalUsers = array();
}
if (!isset($_SESSION) && !utils::IsModeCLI())
{
session_name('itop-'.md5(APPROOT));
session_start();
}
self::_ResetSessionCache();
return self::$m_oAddOn->FlushPrivileges();
if (self::$m_oAddOn)
{
self::$m_oAddOn->FlushPrivileges();
}
}
static $m_aCacheUsers;
@@ -1165,6 +1244,10 @@ class UserRights
{
unset($_SESSION['profile_list']);
}
if (isset($_SESSION['archive_allowed']))
{
unset($_SESSION['archive_allowed']);
}
}
}
@@ -1670,5 +1753,4 @@ class CAS_SelfRegister implements iSelfRegister
}
// By default enable the 'CAS_SelfRegister' defined above
UserRights::SelectSelfRegister('CAS_SelfRegister');
?>
UserRights::SelectSelfRegister('CAS_SelfRegister');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Value set definitions (from a fixed list or from a query, etc.)
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -52,7 +52,7 @@ abstract class ValueSetDefinition
}
public function GetValues($aArgs, $sContains = '')
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded)
{
@@ -93,12 +93,16 @@ abstract class ValueSetDefinition
class ValueSetObjects extends ValueSetDefinition
{
protected $m_sContains;
protected $m_sOperation;
protected $m_sFilterExpr; // in OQL
protected $m_sValueAttCode;
protected $m_aOrderBy;
protected $m_aExtraConditions;
private $m_bAllowAllData;
private $m_aModifierProperties;
private $m_bSort;
private $m_iLimit;
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
@@ -106,12 +110,15 @@ class ValueSetObjects extends ValueSetDefinition
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
$this->m_sOperation = '';
$this->m_sFilterExpr = $sFilterExp;
$this->m_sValueAttCode = $sValueAttCode;
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_aExtraConditions = array();
$this->m_bSort = true;
$this->m_iLimit = 0;
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
@@ -124,7 +131,7 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_aExtraConditions[] = $oFilter;
}
public function ToObjectSet($aArgs = array(), $sContains = '')
public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
if ($this->m_bAllowAllData)
{
@@ -145,15 +152,29 @@ class ValueSetObjects extends ValueSetDefinition
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
if ($iAdditionalValue > 0)
{
$oSearchAdditionalValue = new DBObjectSearch($oFilter->GetClass());
$oSearchAdditionalValue->AddConditionExpression( new BinaryExpression(
new FieldExpression('id', $oSearchAdditionalValue->GetClassAlias()),
'=',
new VariableExpression('current_extkey_id'))
);
$oSearchAdditionalValue->AllowAllData();
$oSearchAdditionalValue->SetArchiveMode(true);
$oSearchAdditionalValue->SetInternalParams( array('current_extkey_id' => $iAdditionalValue) );
$oFilter = new DBUnionSearch(array($oFilter, $oSearchAdditionalValue));
}
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
}
public function GetValues($aArgs, $sContains = '')
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains))
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
{
$this->LoadValues($aArgs, $sContains);
$this->LoadValues($aArgs, $sContains, $sOperation);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
@@ -161,9 +182,10 @@ class ValueSetObjects extends ValueSetDefinition
return $aRet;
}
protected function LoadValues($aArgs, $sContains = '')
protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains')
{
$this->m_sContains = $sContains;
$this->m_sOperation = $sOperation;
$this->m_aValues = array();
@@ -188,12 +210,54 @@ class ValueSetObjects extends ValueSetDefinition
}
}
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
switch ($sOperation)
{
case 'equals_start_with':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($oFilter->GetClass());
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
// Equals first
$oValueExpr = new ScalarExpression($sContains);
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// start with next
$oValueExpr = new ScalarExpression($sContains.'%');
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
break;
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
default:
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
break;
}
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode))
{
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
}
else
{
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch())
{
if (empty($this->m_sValueAttCode))
@@ -217,79 +281,21 @@ class ValueSetObjects extends ValueSetDefinition
{
return $this->m_sFilterExpr;
}
}
/**
* Set of existing values for a link set attribute, given a relation code
*
* @package iTopORM
*/
class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition
{
protected $m_sLinkSetAttCode;
protected $m_sExtKeyToRemote;
protected $m_sRelationCode;
protected $m_iMaxDepth;
protected $m_sTargetClass;
protected $m_sTargetExtKey;
// protected $m_aOrderBy;
public function __construct($sLinkSetAttCode, $sExtKeyToRemote, $sRelationCode, $iMaxDepth, $sTargetClass, $sTargetLinkClass, $sTargetExtKey)
/**
* @param $iLimit
*/
public function SetLimit($iLimit)
{
$this->m_sLinkSetAttCode = $sLinkSetAttCode;
$this->m_sExtKeyToRemote = $sExtKeyToRemote;
$this->m_sRelationCode = $sRelationCode;
$this->m_iMaxDepth = $iMaxDepth;
$this->m_sTargetClass = $sTargetClass;
$this->m_sTargetLinkClass = $sTargetLinkClass;
$this->m_sTargetExtKey = $sTargetExtKey;
// $this->m_aOrderBy = $aOrderBy;
$this->m_iLimit = $iLimit;
}
protected function LoadValues($aArgs)
/**
* @param $bSort
*/
public function SetSort($bSort)
{
$this->m_aValues = array();
if (!array_key_exists('this', $aArgs))
{
throw new CoreException("Missing 'this' in arguments", array('args' => $aArgs));
}
$oTarget = $aArgs['this->object()'];
// Nodes from which we will start the search for neighbourhood
$oNodes = DBObjectSet::FromLinkSet($oTarget, $this->m_sLinkSetAttCode, $this->m_sExtKeyToRemote);
// Neighbours, whatever their class
$aRelated = $oNodes->GetRelatedObjects($this->m_sRelationCode, $this->m_iMaxDepth);
$sRootClass = MetaModel::GetRootClass($this->m_sTargetClass);
if (array_key_exists($sRootClass, $aRelated))
{
$aLinksToCreate = array();
foreach($aRelated[$sRootClass] as $iKey => $oObject)
{
if (MetaModel::IsParentClass($this->m_sTargetClass, get_class($oObject)))
{
$oNewLink = MetaModel::NewObject($this->m_sTargetLinkClass);
$oNewLink->Set($this->m_sTargetExtKey, $iKey);
//$oNewLink->Set('role', 'concerned by an impacted CI');
$aLinksToCreate[] = $oNewLink;
}
}
// #@# or AddObjectArray($aObjects) ?
$oSetToCreate = DBObjectSet::FromArray($this->m_sTargetLinkClass, $aLinksToCreate);
$this->m_aValues[$oObject->GetKey()] = $oObject->GetName();
}
return true;
}
public function GetValuesDescription()
{
return 'Filter: '.$this->m_sFilterExpr;
$this->m_bSort = $bSort;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Bulk export: XML export
*
* @copyright Copyright (C) 2015 Combodo SARL
* @copyright Copyright (C) 2015-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -118,7 +118,7 @@ class XMLBulkExport extends BulkExport
{
continue;
}
if (!$oAttDef->IsWritable())
if ($oAttDef->IsExternalField())
{
continue;
}
@@ -138,7 +138,7 @@ class XMLBulkExport extends BulkExport
$aClass2Attributes[$sAlias] = $aAttributes;
}
}
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');

View File

@@ -5,4 +5,4 @@ $complement-light: #d6e8ef;
$frame-background-color: #F1F1F1;
$text-color: #000;
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.3.0";
$version: "v2.4.0";

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -21,7 +21,7 @@ OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 1
CreationTime: 1463745065
ModificationTime: 1464178421
ModificationTime: 1506001058
OS2TypoAscent: 0
OS2TypoAOffset: 1
OS2TypoDescent: 0
@@ -35,7 +35,6 @@ HheadAscent: 0
HheadAOffset: 1
HheadDescent: 0
HheadDOffset: 1
OS2Vendor: 'PfEd'
MarkAttachClasses: 1
DEI: 91125
Encoding: ISO8859-1
@@ -47,7 +46,7 @@ FitToEm: 0
WinInfo: 0 31 10
BeginPrivate: 0
EndPrivate
BeginChars: 256 8
BeginChars: 256 11
StartChar: zero
Encoding: 48 48 0
@@ -210,7 +209,7 @@ StartChar: three
Encoding: 51 51 3
Width: 1022
VWidth: 0
Flags: MO
Flags: M
LayerCount: 3
Fore
SplineSet
@@ -261,11 +260,13 @@ StartChar: C
Encoding: 67 67 4
Width: 1080
VWidth: 0
Flags: HW
Flags: W
HStem: -112 36<398.67 444.211> 97 36<463.993 575.071> 116 37<411.524 459.906> 250 37<334.123 402.464> 335 37<749.246 821.773> 387 37<836.543 929.295> 396 37<873.093 933.545> 442 37<475 482 739.647 795.664> 621 37<286.042 389.13> 650 37<510.192 579.789>
VStem: 53 37<216.048 298> 218 37<457.867 579.651> 272 36<60.6299 128.446> 439 37<479.452 577.67> 451 37<-73.2171 5.85426> 472 37<568.829 649.107> 553 37<359.872 417.574> 651 38<-34.3438 62.6665> 718 38<8.82031 164.606> 934 38<341.569 396>
LayerCount: 3
Fore
SplineSet
641 -116 m 4x9b20
641 -116 m 4x9b39f0
637 -116 633 -115 630 -113 c 4
624 -110 616 -102 616 -88 c 4
616 -84 617 -80 618 -75 c 4
@@ -275,14 +276,14 @@ SplineSet
651 31 641 52 624 75 c 4
609 94 595 108 586 115 c 5
562 107 523 97 500 97 c 6
499 97 l 6xdb20
499 97 l 6xdb39f0
479 97 468 105 460 111 c 4
457 114 455 115 453 116 c 4
450 116 438 112 423 104 c 4
412 98 405 94 401 90 c 5
411 80 436 61 448 52 c 4
460 43 468 37 473 32 c 4
485 20 488 -4 488 -20 c 4
485 20 488 -4 488 -20 c 4xb93af0
488 -27 487 -33 487 -36 c 4
485 -55 477 -90 452 -105 c 4
444 -110 435 -112 426 -112 c 4
@@ -321,10 +322,10 @@ SplineSet
219 484 218 493 218 504 c 4
218 526 222 550 231 575 c 4
237 593 255 636 284 651 c 4
293 656 305 658 320 658 c 4xb9a0
293 656 305 658 320 658 c 4
342 658 367 653 389 644 c 4
416 633 437 617 452 597 c 4
472 571 476 537 476 508 c 4
472 571 476 537 476 508 c 4xb9bcf0
476 497 476 487 475 479 c 5
482 479 l 5
505 499 l 5
@@ -352,7 +353,7 @@ SplineSet
766 473 767 473 768 473 c 4
787 473 808 460 831 446 c 4
845 437 865 424 873 424 c 4
874 424 l 6x9d60
874 424 l 6x9d79f0
875 424 880 425 884 426 c 4
897 429 915 433 930 433 c 4
956 433 965 421 969 412 c 4
@@ -362,7 +363,7 @@ SplineSet
916 289 911 288 905 288 c 4
888 288 863 299 835 311 c 4
810 322 780 335 766 335 c 4
765 335 764 335 764 335 c 4
764 335 l 4
763 335 757 331 748 315 c 4
741 301 734 283 727 264 c 4
721 248 715 231 708 216 c 5
@@ -370,7 +371,7 @@ SplineSet
756 69 755 58 753 47 c 4
748 19 727 -22 708 -51 c 4
697 -68 687 -82 677 -93 c 4
663 -109 652 -116 641 -116 c 4x9b20
663 -109 652 -116 641 -116 c 4x9b39f0
308 85 m 5
308 83 312 72 340 46 c 4
357 30 374 18 374 18 c 6
@@ -387,14 +388,14 @@ SplineSet
383 55 362 72 362 90 c 4
362 92 362 93 362 95 c 4
363 100 366 113 402 134 c 4
410 138 435 153 453 153 c 4xb9a0
410 138 435 153 453 153 c 4xb9baf0
455 153 458 152 460 152 c 4
470 150 476 145 482 141 c 4
488 136 491 133 499 133 c 6
500 133 l 6xd920
500 133 l 6xd93af0
521 133 562 145 581 153 c 6
589 156 l 5
596 153 l 6xb920
596 153 l 6xb93af0
612 146 637 119 654 97 c 4
670 75 689 44 689 16 c 4
689 13 689 10 688 7 c 4
@@ -413,7 +414,7 @@ SplineSet
868 337 893 326 903 325 c 5
907 329 916 340 924 359 c 4
932 377 934 390 934 396 c 4
933 396 932 396 930 396 c 4xdb20
933 396 932 396 930 396 c 4xdb3af0
919 396 902 392 892 390 c 4
886 389 881 388 878 388 c 4
876 388 875 387 873 387 c 4
@@ -433,9 +434,9 @@ SplineSet
629 559 619 566 619 566 c 6
615 569 l 5
580 633 l 5
566 639 539 650 520 650 c 4x9d60
566 639 539 650 520 650 c 4
514 650 512 650 511 649 c 4
510 648 509 644 509 637 c 4
510 648 509 644 509 637 c 4x9d79f0
509 629 510 618 514 602 c 4
517 589 520 577 523 569 c 5
562 561 l 5
@@ -446,7 +447,7 @@ SplineSet
435 465 l 6
437 474 439 491 439 510 c 4
439 533 436 558 423 575 c 4
400 605 354 621 320 621 c 4x99a0
400 605 354 621 320 621 c 4x99bcf0
310 621 304 619 301 618 c 4
281 608 255 548 255 504 c 4
255 497 255 490 257 484 c 4
@@ -475,22 +476,25 @@ SplineSet
424 204 390 196 377 186 c 4
330 149 310 105 308 85 c 5
EndSplineSet
Validated: 1
EndChar
StartChar: I
Encoding: 73 73 5
Width: 1024
VWidth: 0
Flags: HW
Flags: W
HStem: -154 166<226 365 659 798> 126 26<498.267 525.733> 151 131<288 343 681 735> 313 132<288 343 681 735> 330 32<424.389 599.754> 443 26<498.267 525.733> 584 184<226 365 659 798>
VStem: 51 175<12 151 445 584> 365 132<74 126 469 521> 366 31<282 313> 527 132<74 126 469 521> 627 31<282 313> 798 175<12 151 445 584>
LayerCount: 3
Fore
SplineSet
51 -154 m 1
51 -154 m 1x8308
51 768 l 1
973 768 l 1
973 -154 l 1
51 -154 l 1
497 469 m 2
51 -154 l 1x8308
497 469 m 1x87a8
502 469 507 470 512 470 c 0
517 470 522 469 527 469 c 1
527 521 l 1
@@ -498,35 +502,34 @@ SplineSet
512 702 l 1
414 604 l 1
497 521 l 1
497 469 l 1
497 469 l 2
497 469 l 1x87a8
653 417 m 1
681 445 l 1
798 445 l 1
798 445 l 1x9328
798 584 l 1
659 584 l 1
659 467 l 1
633 440 l 1
643 433 649 425 653 417 c 1
366 282 m 1
366 282 m 1xb348
366 313 l 1
288 313 l 1
206 396 l 1
108 298 l 1
206 200 l 1
288 282 l 1
366 282 l 1
366 282 l 1xb348
343 445 m 1
371 417 l 1
375 425 381 433 391 440 c 1
365 467 l 1
365 584 l 1
365 584 l 1x9388
226 584 l 1
226 445 l 1
343 445 l 1
371 178 m 1
343 151 l 1
226 151 l 1
226 151 l 1xa388
226 12 l 1
365 12 l 1
365 129 l 1
@@ -535,7 +538,7 @@ SplineSet
818 396 m 1
735 313 l 1
658 313 l 1
658 282 l 1
658 282 l 1xb318
735 282 l 1
818 200 l 1
916 298 l 1
@@ -543,12 +546,12 @@ SplineSet
653 178 m 1
649 170 643 162 633 155 c 1
659 129 l 1
659 12 l 1
659 12 l 1xa328
798 12 l 1
798 151 l 1
681 151 l 1
653 178 l 1
527 126 m 1
527 126 m 1xc3a8
522 126 517 126 512 126 c 0
507 126 502 126 497 126 c 1
497 74 l 1
@@ -556,8 +559,7 @@ SplineSet
512 -106 l 1
610 -8 l 1
527 74 l 1
527 126 l 1
527 126 l 1
527 126 l 1xc3a8
610 348 m 0
584 337 549 330 512 330 c 0
475 330 441 337 414 348 c 0
@@ -567,9 +569,9 @@ SplineSet
449 157 479 152 512 152 c 0
545 152 575 157 598 167 c 0
616 174 627 184 627 192 c 2
627 356 l 1
627 356 l 1xcb58
622 353 616 351 610 348 c 0
512 443 m 1
512 443 m 1x8f58
479 443 449 438 426 428 c 0
408 421 397 410 397 402 c 0
397 394 408 384 426 377 c 0
@@ -577,8 +579,7 @@ SplineSet
545 362 575 367 598 377 c 0
616 384 627 394 627 402 c 0
627 410 616 421 598 428 c 0
575 438 545 443 512 443 c 1
512 443 l 1
575 438 545 443 512 443 c 1x8f58
EndSplineSet
Validated: 5
EndChar
@@ -587,7 +588,8 @@ StartChar: four
Encoding: 52 52 6
Width: 1024
VWidth: 0
Flags: H
HStem: -2 41<389.544 635.489> 292 109<316 441 550 675> 639 41<389.544 636.396>
VStem: 117 41<241.556 436.857> 441 109<167 292 401 525> 868 41<249.643 435.223>
LayerCount: 3
Fore
SplineSet
@@ -607,7 +609,6 @@ SplineSet
909 288 892 238 861 189 c 0
835 149 801 111 757 77 c 1
801 -67 l 1
801 -67 l 1
649 63 m 1
733 17 l 1
710 93 l 1
@@ -618,7 +619,6 @@ SplineSet
158 174 317 39 513 39 c 0
552 39 592 49 634 59 c 2
649 63 l 1
649 63 l 1
675 306 m 1
675 298 667 292 657 292 c 2
550 292 l 1
@@ -640,7 +640,6 @@ SplineSet
657 401 l 2
667 401 675 394 675 386 c 2
675 306 l 1
675 306 l 1
EndSplineSet
Validated: 5
EndChar
@@ -649,15 +648,17 @@ StartChar: D
Encoding: 68 68 7
Width: 1080
VWidth: 0
Flags: HW
Flags: W
HStem: 198 131<79.9424 131.341> 306 98<835.102 921.871> 353 100<731.982 810.871>
VStem: 238 219<460.138 583.719> 292 88<67.6511 114.863> 369 100<-72.2559 6.9753> 667 66<3.74069 76.8123>
LayerCount: 3
Fore
SplineSet
469 -14 m 4
469 -14 m 4x26
469 -45 459 -89 425 -89 c 4
397 -89 372 -53 369 -41 c 4
397 -89 372 -53 369 -41 c 4x26
365 -27 363 7 363 7 c 5
363 7 292 63 292 88 c 4
363 7 292 63 292 88 c 4x2a
292 113 317 164 366 202 c 4
388 219 439 225 439 225 c 5
439 225 414 269 373 269 c 4
@@ -669,7 +670,7 @@ SplineSet
119 206 108 198 97 198 c 4
78 198 76 234 76 261 c 4
76 268 76 274 76 279 c 4
76 288 76 295 76 295 c 6
76 295 l 6
76 295 82 320 96 328 c 4
97 328 98 329 99 329 c 4
121 329 217 268 217 268 c 6
@@ -696,12 +697,12 @@ SplineSet
570 401 569 394 569 388 c 4
569 355 614 330 614 330 c 5
614 330 646 364 662 384 c 4
678 404 734 453 763 453 c 4
678 404 734 453 763 453 c 4xb2
790 453 841 404 867 404 c 4
878 404 902 413 924 413 c 4
938 413 945 409 945 396 c 4
945 371 922 318 905 307 c 4
903 306 902 306 899 306 c 4
903 306 902 306 899 306 c 4x52
872 306 795 353 761 353 c 4
758 353 756 353 754 352 c 4
723 342 708 263 682 213 c 5
@@ -714,11 +715,232 @@ SplineSet
667 64 605 129 586 138 c 5
565 130 522 118 499 118 c 4
475 118 470 137 453 137 c 4
437 137 380 108 380 93 c 4
437 137 380 108 380 93 c 4x2a
380 77 446 36 460 23 c 4
465 18 469 3 469 -14 c 4
465 18 469 3 469 -14 c 4x26
EndSplineSet
Validated: 1
EndChar
StartChar: E
Encoding: 69 69 8
Width: 1024
VWidth: 0
HStem: -65 248<767.992 903.557> 62.9598 56.9516<226.56 349.224 705.189 710> 143 26.9554<256.859 324.845> 192.776 27.2245<289.957 314.952> 295.071 27.9295<269.575 354.789> 344 66<376.038 439.962> 433 25<407.015 431.988> 469 248<767.951 904.873>
VStem: 66.5097 115.469<193.203 298.14> 204 32.9583<187.285 267.499> 257.966 57.0345<194.835 224.243> 335.831 30.8205<177.895 239.59> 388.713 44.4108<157.773 270.37> 407 25<433.012 457.985> 442 55.4336<365.791 447.874> 711 249<-8.06335 62 583 662.557>
LayerCount: 3
Fore
SplineSet
279 573 m 0x7ffb
292 573 313 570 343 563 c 0
462 535 489 420 496 389 c 0
496.963 384.737 497.434 380.795 497.434 377.167 c 0
497.434 354.414 478.912 344 447 344 c 0
422.333 344 394.111 344.889 359.074 344.889 c 0
341.556 344.889 322.333 344.667 301 344 c 0
237 342 192 300 183 241 c 0
182.31 236.477 181.979 231.948 181.979 227.44 c 0
181.979 173.142 230.063 121.847 279 120 c 0
280.57 119.941 282.134 119.911 283.69 119.911 c 0
334.636 119.911 377.296 151.452 387 199 c 0
388.159 204.678 388.713 210.303 388.713 215.813 c 0
388.713 257.852 356.437 293.232 314 295 c 0
312.865 295.047 311.742 295.071 310.63 295.071 c 0
264.267 295.071 236.958 255.02 236.958 229.128 c 0
236.958 206.914 253.918 170.972 286 170 c 0
287.107 169.97 288.186 169.955 289.238 169.955 c 0
323.358 169.955 329.15 185.419 334 199 c 0
335.206 202.378 335.831 206.221 335.831 210.207 c 0
335.831 222.743 329.657 236.689 316 242 c 0
298 249 291 238 290 234 c 0
289 228 294 220 299 220 c 0
306 220 316 220 315 206 c 0
314.143 195.714 302.265 192.776 292.592 192.776 c 0
290.98 192.776 289.429 192.857 288 193 c 0
278.231 193.977 257.966 202.588 257.966 228.155 c 0
257.966 248.46 275.443 271.256 306.289 271.256 c 0
308.143 271.256 310.047 271.172 312 271 c 0
351.28 267.429 366.651 246.325 366.651 219.783 c 0
366.651 216.594 366.429 213.326 366 210 c 0
362 179 343 143 284 143 c 0
225 143 204 192 204 229 c 0
204 266 232 322 318 323 c 0
318.502 323.006 319.002 323.009 319.501 323.009 c 0
394.354 323.009 433.124 257.543 433.124 205.254 c 0
433.124 198.198 432.418 191.383 431 185 c 0
420.148 134.686 398.59 62.9598 273.187 62.9598 c 0
271.478 62.9598 269.749 62.9731 268 63 c 0
134.539 65.8599 66.5097 196.855 66.5097 295.718 c 0
66.5097 300.562 66.673 305.329 67 310 c 0
74 410 112 447 146 480 c 0
204.987 537.114 264.85 531.109 264.85 543.245 c 0
264.85 544.071 264.573 544.981 264 546 c 0
260.913 551.732 257.827 557.659 257.827 562.495 c 0
257.827 568.625 262.788 573 279 573 c 0x7ffb
408 478 m 0
389 478 374 463 374 444 c 0
374 425 389 410 408 410 c 0
427 410 442 425 442 444 c 0
442 463 427 478 408 478 c 0
835 717 m 0
904 717 960 662 960 593 c 0
960 524 904 469 835 469 c 0
798 469 751 495 731 526 c 1
515 456 l 1
503 483 491 498 481 511 c 1
712 583 l 2
712 586 711 590 711 593 c 0
711 662 766 717 835 717 c 0
462 207 m 1
727 121 l 1
747 155 795 183 834 183 c 0
903 183 958 128 958 59 c 0
958 -10 903 -65 834 -65 c 0x9ff3
765 -65 710 -10 710 59 c 0
710 60 710 61 710 62 c 2
456 143 l 1
458.679 159.077 462.157 171.165 462.157 197.076 c 0
462.157 200.176 462.107 203.474 462 207 c 1
407 445 m 0x1ff7
407 453 412 458 420 458 c 0
428 458 432 453 432 445 c 0
432 437 428 433 420 433 c 0
412 433 407 437 407 445 c 0x1ff7
EndSplineSet
Validated: 1
EndChar
StartChar: F
Encoding: 70 70 9
Width: 1024
VWidth: 0
HStem: -36 87<438.038 591.036> 87 39<470.027 570.5> 163 41<517.914 555.989> 241 41<519.089 577.135> 317 43<482.813 612.995> 394 100<654.807 741.193> 528 39<697.004 734.996>
VStem: 177 177<195.689 296.938> 387 50<156.758 272.234> 469 87<171.273 203.985> 588 47<139.283 231.811> 669 67<122.953 266.087> 697 38<528.004 566.995> 750 85<426.5 559.5>
LayerCount: 3
Fore
SplineSet
502 742 m 0xfff4
522 742 553 738 599 727 c 0
780 684 822 509 833 462 c 0
835 456 835 450 835 444 c 0
835 409 806 392 758 392 c 0
721 392 678 394 624 394 c 0
597 394 568 393 535 392 c 0
437 388 368 325 355 235 c 0
354 228 354 222 354 215 c 0
354 132 426 55 501 51 c 0
504 51 507 51 510 51 c 0
587 51 652 99 666 171 c 0
668 180 669 188 669 196 c 0
669 260 620 313 555 317 c 0
553 317 550 317 548 317 c 0
478 317 437 256 437 218 c 0
437 184 463 127 512 126 c 0
514 126 516 126 518 126 c 0
569 126 578 150 586 171 c 0
588 176 588 181 588 187 c 0
588 207 579 228 557 237 c 0
550 240 545 241 540 241 c 0
525 241 520 230 518 225 c 0
516 216 524 204 532 204 c 0
543 204 556 202 556 183 c 0
556 166 541 163 525 163 c 0
522 163 518 163 515 163 c 0
499 164 469 177 469 217 c 0
469 247 497 282 543 282 c 0
546 282 549 282 552 282 c 0
611 277 635 243 635 202 c 0
635 197 635 193 634 188 c 0
628 141 598 87 508 87 c 0
418 87 387 162 387 218 c 0
387 274 430 358 561 360 c 0
562 360 563 360 564 360 c 0
677 360 736 262 736 182 c 0
736 171 735 160 733 150 c 0
716 74 683 -36 494 -36 c 0
491 -36 487 -36 484 -36 c 0
280 -32 177 170 177 321 c 0
177 328 178 335 178 342 c 0
188 495 246 549 298 600 c 0
388 687 479 677 479 696 c 0
479 697 479 698 478 700 c 0
473 709 469 718 469 725 c 0
469 735 477 742 502 742 c 0xfff4
698 597 m 0
669 597 646 574 646 545 c 0
646 516 669 494 698 494 c 0
727 494 750 516 750 545 c 0
750 574 727 597 698 597 c 0
697 547 m 0xffec
697 560 703 567 716 567 c 0
729 567 735 560 735 547 c 0
735 534 729 528 716 528 c 0
703 528 697 534 697 547 c 0xffec
EndSplineSet
Validated: 1
EndChar
StartChar: O
Encoding: 79 79 10
Width: 1024
VWidth: 0
HStem: 1.59961 8<801.97 829.007> 20 35.2002<801.975 810.774 823.574 824.837> 33.5996 5.60059<810.774 817.806> 48.7998 6.40039<810.774 818.736> 61.5996 8.7998<801.043 829.91> 434.399 245.601<441.712 567.228>
VStem: 780.375 8.7998<21.8033 49.6942> 801.975 8.7998<20 33.5996 39.2002 48.7998> 819.574 8.80078<40.0451 47.9513> 841.175 8.7998<21.5374 50.3356>
LayerCount: 3
Fore
SplineSet
504.375 680 m 0x8fc0
672.375 680 816.375 560 846.774 395.2 c 0
877.175 230.399 785.975 67.2002 629.175 7.2002 c 0
621.975 4.7998 613.975 8 611.574 15.2002 c 2
533.175 218.399 l 2
530.774 225.6 533.975 234.399 541.175 236.8 c 0
587.574 254.399 613.975 301.6 605.175 350.399 c 0
596.375 399.2 553.975 434.399 504.375 434.399 c 0
454.774 434.399 413.175 399.2 404.375 350.399 c 0
395.574 301.6 421.175 254.399 467.574 236.8 c 0
474.774 234.399 477.975 225.6 475.574 218.399 c 2
397.975 15.2002 l 2
395.574 8 386.774 4.7998 379.574 7.2002 c 0
222.774 67.2002 131.574 230.399 161.975 395.2 c 0
192.375 560 336.375 680 504.375 680 c 0x8fc0
815.574 70.3994 m 0
834.774 70.3994 849.975 55.2002 849.975 36 c 0
849.975 16.7998 834.774 1.59961 815.574 1.59961 c 0
796.375 1.59961 780.375 16.7998 780.375 36 c 0
780.375 55.2002 796.375 70.3994 815.574 70.3994 c 0
815.574 61.5996 m 0
801.175 61.5996 789.175 50.3994 789.175 36 c 0
789.175 21.5996 801.175 9.59961 815.574 9.59961 c 0
829.975 9.59961 841.175 21.5996 841.175 36 c 0
841.175 50.3994 829.975 61.5996 815.574 61.5996 c 0
814.774 39.2002 m 2xbfc0
816.375 39.2002 817.975 40 818.774 40.7998 c 0
819.574 41.5996 819.574 42.3994 819.574 44 c 0
819.574 45.5996 819.574 46.3994 818.774 47.2002 c 0
817.975 48 816.375 48.7998 814.774 48.7998 c 2
810.774 48.7998 l 1
810.774 39.2002 l 1
814.774 39.2002 l 2xbfc0
810.774 33.5996 m 1
810.774 20 l 1
801.975 20 l 1
801.975 55.2002 l 1xcfc0
815.574 55.2002 l 2x9fc0
820.375 55.2002 822.774 54.3994 825.175 52.7998 c 0
827.574 51.2002 828.375 48.7998 828.375 45.5996 c 0
828.375 43.2002 827.574 41.5996 826.774 40 c 0
825.975 38.3994 824.375 36.7998 821.975 36 c 1
823.574 36 824.375 35.2002 825.175 34.3994 c 0
825.975 33.5996 827.574 32 828.375 29.5996 c 2
833.175 20 l 1
823.574 20 l 1xcfc0
819.574 28.7998 l 2
818.774 30.3994 817.175 31.2002 816.375 32 c 0
815.574 32.7998 814.774 33.5996 813.175 33.5996 c 2
810.774 33.5996 l 1
EndSplineSet
Validated: 524321
EndChar
EndChars
EndSplineFont

Binary file not shown.

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: 'CombodoRegular';
src: url('combodo-webfont.woff2?v=1.0') format('woff2'),
url('combodo-webfont.woff?v=1.0') format('woff'),
url('combodo-webfont.ttf?v=1.0') format('truetype');
src: url('combodo-webfont.woff2?v=2.0') format('woff2'),
url('combodo-webfont.woff?v=2.0') format('woff'),
url('combodo-webfont.ttf?v=2.0') format('truetype');
font-weight: normal;
font-style: normal;
@@ -193,7 +193,16 @@
.fc-combodo-icon:before {
content: "D";
}
.fc-itophub-icon:before {
content: "E";
}
.fc-chameleon-icon:before {
content: "F";
}
.fc-itop-icon:before {
content: "I";
}
.fc-opensource-icon:before {
content: "O";
}

50
css/font-combodo/glyphs/E.svg Executable file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="1000"
height="1000"
viewBox="0 0 264.58333 264.58334"
version="1.1"
id="svg909">
<defs
id="defs903" />
<metadata
id="metadata906">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="fill:#000000;fill-opacity:1"
transform="matrix(2.6189878,0,0,2.6189878,-138.11438,-66.566639)"
id="layer1">
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.40000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 77.776825,48.628779 c -3.105953,0.0165 -2.465143,1.56905 -1.672249,3.00189 0.969595,1.75214 -6.046384,0.53457 -12.856064,7.17372 -3.69523,3.60269 -7.8302,7.46233 -8.56485,18.39112 -0.73465,10.9288 6.57401,26.601051 21.834325,26.890351 14.098468,0.26727 16.523001,-7.762201 17.760671,-13.268961 1.323287,-5.88768 -2.939907,-15.10068 -12.294338,-14.96704 -9.354428,0.13364 -12.428178,6.14692 -12.428178,10.15597 0,4.00904 2.27182,9.35447 8.686288,9.35447 6.414468,0 8.552552,-3.87524 8.953457,-7.2161 0.400905,-3.34087 -1.12866,-6.23284 -5.863206,-6.68176 -3.664545,-0.34747 -5.823431,2.30622 -5.896281,4.44314 -0.100228,2.93996 2.194808,3.98392 3.307291,4.04264 1.112483,0.0587 2.766575,-0.003 2.856155,-1.33635 0.10023,-1.49182 -0.935023,-1.63676 -1.686717,-1.62005 -0.534409,0.0119 -1.147985,-0.88438 -0.985986,-1.50379 0.121989,-0.46643 0.78549,-1.62036 2.790009,-0.81855 2.004523,0.8018 2.544982,3.13823 2.021065,4.67723 -0.534538,1.57021 -1.18617,3.2909 -5.228621,3.17397 -3.536694,-0.1023 -5.411874,-4.25964 -5.328354,-6.68177 0.0939,-2.724 3.190312,-7.1826 8.402068,-6.91534 5.211754,0.26728 8.953352,5.07775 7.884274,10.42314 -1.069079,5.34539 -6.013196,8.82024 -11.759488,8.55297 -5.746294,-0.26727 -11.359094,-6.68189 -10.423654,-13.09636 0.93544,-6.41447 5.87967,-10.95789 12.828675,-11.22516 6.949005,-0.26727 11.885592,0.016 15.894636,0.016 4.009047,0 6.155487,-1.61955 5.353677,-4.96042 -0.8018,-3.34086 -3.786135,-15.85543 -16.689419,-18.91977 -3.29736,-0.78307 -5.483391,-1.09268 -6.895186,-1.0852 z m 13.978992,10.35854 a 3.685268,3.685268 0 0 1 3.685563,3.68505 3.685268,3.685268 0 0 1 -3.685563,3.68556 3.685268,3.685268 0 0 1 -3.685048,-3.68556 3.685268,3.685268 0 0 1 3.685048,-3.68505 z"
id="path882" />
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.40000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 138.24445,32.953249 a 13.49711,13.49711 0 0 0 -13.49685,13.49685 13.49711,13.49711 0 0 0 0.0465,1.06195 l -25.043431,7.84087 c 1.104251,1.4075 2.257151,3.10658 3.611151,6.01255 l 23.55566,-7.5799 a 13.49711,13.49711 0 0 0 11.32696,6.1619 13.49711,13.49711 0 0 0 13.49737,-13.49737 13.49711,13.49711 0 0 0 -13.49737,-13.49685 z"
id="rect827" />
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.40000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 97.624193,88.491979 c 0.07421,3.57897 -0.303877,5.04652 -0.587044,6.95772 l 27.570411,8.739001 a 13.49711,13.49711 0 0 0 -0.0134,0.37258 13.49711,13.49711 0 0 0 13.49737,13.49737 13.49711,13.49711 0 0 0 13.49685,-13.49737 13.49711,13.49711 0 0 0 -13.49685,-13.496851 13.49711,13.49711 0 0 0 -11.65201,6.71794 z"
id="rect827-1" />
<circle
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.40000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path888"
cx="93.031555"
cy="62.530666"
r="1.3701637" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

41
css/font-combodo/glyphs/F.svg Executable file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="1000"
height="1000"
viewBox="0 0 264.58333 264.58334"
version="1.1"
id="svg909">
<defs
id="defs903" />
<metadata
id="metadata906">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(0,-32.41665)"
id="layer1">
<path
style="fill:#1b1b1b;fill-opacity:1;stroke:#000000;stroke-width:1.71210456;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 128.74663,48.188011 c -13.29429,0.07062 -10.55146,6.715944 -7.15766,12.848874 4.15012,7.499617 -25.880108,2.288099 -55.027316,30.705397 -15.81655,15.420458 -33.515303,31.940728 -36.659797,78.718798 -3.144494,46.77813 28.138482,113.85946 93.456613,115.09774 60.34513,1.14398 70.72277,-33.22425 76.02032,-56.79463 5.66401,-25.20081 -12.58357,-64.63485 -52.62298,-64.06284 -40.0394,0.57201 -53.195848,26.31042 -53.195848,43.47021 0,17.15974 9.723978,40.03957 37.179578,40.03957 27.4556,0 36.60716,-16.58704 38.32314,-30.88679 1.71598,-14.2998 -4.83096,-26.67818 -25.09606,-28.59968 -15.68521,-1.48726 -24.9258,9.87122 -25.23762,19.0178 -0.429,12.5838 9.39435,17.05222 14.15607,17.30356 4.76172,0.25125 11.84166,-0.0128 12.22509,-5.71993 0.42901,-6.38538 -4.00214,-7.00576 -7.21959,-6.93424 -2.28741,0.0509 -4.91368,-3.78538 -4.22028,-6.43661 0.52215,-1.99644 3.3621,-6.93557 11.94197,-3.50361 8.57988,3.43191 10.89319,13.43244 8.65069,20.01977 -2.28797,6.72091 -5.07712,14.08591 -22.37987,13.58542 -15.13797,-0.43787 -23.16423,-18.23237 -22.80675,-28.59972 0.40192,-11.65944 13.65537,-30.74341 35.96305,-29.59947 22.30767,1.14403 38.32269,21.7341 33.74675,44.61377 -4.57593,22.87966 -25.73805,37.75293 -50.33368,36.60894 -24.59564,-1.14398 -48.619888,-28.60023 -44.615958,-56.05584 4.00393,-27.45561 25.166518,-46.90263 54.910078,-48.04662 29.74356,-1.14398 50.87344,0.0685 68.0332,0.0685 17.15977,0 26.34709,-6.93209 22.91514,-21.23189 -3.43192,-14.29975 -16.20565,-67.865386 -71.43508,-80.981563 -14.11356,-3.351744 -23.47035,-4.676956 -29.5132,-4.644939 z m 59.83374,44.337259 a 15.77391,15.77391 0 0 1 15.77517,15.77298 15.77391,15.77391 0 0 1 -15.77517,15.77516 15.77391,15.77391 0 0 1 -15.77297,-15.77516 15.77391,15.77391 0 0 1 15.77297,-15.77298 z"
id="path882" />
<circle
style="opacity:1;fill:#101010;fill-opacity:1;stroke:#000000;stroke-width:1.71210456;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path888"
cx="194.04086"
cy="107.69173"
r="5.8646588" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

87
css/font-combodo/glyphs/O.svg Executable file
View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1000"
height="1000"
viewBox="0 0 264.58333 264.58334"
version="1.1"
id="svg876"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="O.svg">
<defs
id="defs870" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-272.85714"
inkscape:cy="560"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="1911"
inkscape:window-y="-9"
inkscape:window-maximized="1" />
<metadata
id="metadata873">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-32.41664)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:middle;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8.99672318;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 131.53572,50.179132 c -54.174274,0 -100.702568,38.6765 -110.575582,91.938598 -9.873159,53.26282 19.698102,106.06258 70.277674,125.4761 2.319548,0.89024 4.921621,-0.26836 5.811987,-2.58786 L 122.30183,199.2106 c 0.89023,-2.31957 -0.26838,-4.92164 -2.58789,-5.812 -14.87072,-5.70891 -23.515673,-21.14784 -20.612431,-36.80689 2.903191,-15.6588 16.504681,-26.96534 32.434211,-26.96534 15.92952,0 29.53338,11.30654 32.43657,26.96534 2.90326,15.65905 -5.74171,31.09798 -20.61242,36.80689 -2.31951,0.89036 -3.47812,3.49245 -2.58788,5.812 l 25.25202,65.79537 c 0.89038,2.31821 3.49074,3.47652 5.8096,2.58786 50.57958,-19.41352 80.15083,-72.21328 70.27768,-125.4761 -9.87301,-53.262098 -56.4013,-91.938598 -110.57557,-91.938598 z"
id="path3773"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssccscsssccccss" />
<g
style="display:inline"
id="g3797"
transform="matrix(1.2200654,0,0,1.2200654,455.86197,1099.5367)"
inkscape:export-filename="/home/rafael/workspace/logo-osi/3/png/logo396x412.png"
inkscape:export-xdpi="48.18"
inkscape:export-ydpi="48.18">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.25831962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -183.5617,-698.57555 c -5.02556,0 -9.12392,4.09113 -9.12392,9.10782 0,5.01668 4.09836,9.10781 9.12392,9.10781 5.02556,0 9.12392,-4.09113 9.12392,-9.10781 0,-5.01669 -4.09836,-9.10782 -9.12392,-9.10782 z m 0,2.25596 c 3.80399,0 6.8633,3.05458 6.8633,6.85186 0,3.79727 -3.05931,6.85119 -6.8633,6.85119 -3.80399,0 -6.86397,-3.05392 -6.86397,-6.85119 0,-3.79728 3.05998,-6.85186 6.86397,-6.85186 z"
id="path3015"
inkscape:connector-curvature="0" />
<g
aria-label="R"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:0%;font-family:OpenSymbol;-inkscape-font-specification:'OpenSymbol Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
id="text3793">
<path
inkscape:connector-curvature="0"
d="m -183.7552,-690.46513 q 0.74769,0 1.06902,-0.27807 0.3275,-0.27807 0.3275,-0.91454 0,-0.63028 -0.3275,-0.90217 -0.32133,-0.27189 -1.06902,-0.27189 h -1.00105 v 2.36667 z m -1.00105,1.64369 v 3.4913 h -2.37902 v -9.22569 h 3.63342 q 1.82289,0 2.66946,0.61175 0.85274,0.61175 0.85274,1.93412 0,0.91454 -0.44491,1.50157 -0.43873,0.58703 -1.32855,0.8651 0.48817,0.11123 0.87128,0.50671 0.3893,0.38929 0.78477,1.18642 l 1.29147,2.62002 h -2.53351 l -1.12463,-2.29252 q -0.33986,-0.69208 -0.69208,-0.94543 -0.34604,-0.25335 -0.92689,-0.25335 z"
style="font-size:12.65519524px;line-height:1.25"
id="path863" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -18,6 +18,9 @@ aIcons = {
'combodo-icon': 'Combodo icon',
'combodo-icon-o': 'Combodo icon (outline)',
'itop-icon': 'iTop icon',
'itophub-icon': 'iTop Hub icon',
'chameleon-icon': 'Hub\'s Chameleon icon',
'opensource-icon': 'Open Source Logo',
}
function GenerateTable() {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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