Compare commits

...

157 Commits
tag ... 2.3.4

Author SHA1 Message Date
Guillaume Lajarige
bed0b63c6c Creating tag for iTop 2.3.4
SVN:2.3.4[4910]
2017-09-14 12:47:06 +00:00
Vincent Dumas
5112fa4927 Readme iTop Community 2.3.4
SVN:2.3[4682]
2017-04-14 15:36:18 +00:00
Guillaume Lajarige
7246f957fd Portal: Performance optimization in BrowseBrick by removing unnecessary objects reload.
SVN:2.3[4680]
2017-04-14 10:08:31 +00:00
Vincent Dumas
82552c7c98 Release notes Community 2.3.4
SVN:2.3[4677]
2017-04-07 13:43:58 +00:00
Romain Quetiez
9a010bd02a (Retrofoit from trunk) 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:2.3[4676]
2017-04-07 10:00:34 +00:00
Romain Quetiez
2b40c57c1d (Retrofit from trunk) N.417 Object name displayed with html entities (e.g. "&" shown as "&") when selecting/creating an object into an autocomplete
SVN:2.3[4674]
2017-04-06 18:52:38 +00:00
Romain Quetiez
6bf229e803 (Retrofit from trunk) 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:2.3[4673]
2017-04-06 18:46:52 +00:00
Romain Quetiez
ae5eb9ebff Retrofit from trunk) 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:2.3[4667]
2017-04-04 12:59:43 +00:00
Guillaume Lajarige
b60dbf1389 (Retrofit from trunk) Portal: CaseLog fields value was kept in the form after object update instead of being reset.
SVN:2.3[4665]
2017-04-04 10:18:14 +00:00
Romain Quetiez
39f08d0ce0 (Retrofit from trunk) N.755 Case log latest entry preset with HTML code gets transformed into pure text
SVN:2.3[4663]
2017-04-04 09:26:32 +00:00
Guillaume Lajarige
de68827653 (Retrofit from trunk) N°807 Portal: add_to_list can now be used in action rules (Note: Works only for IndirectLinkedSet, not LinkedSet)
SVN:2.3[4661]
2017-04-04 09:08:36 +00:00
Guillaume Lajarige
dcf94f1161 (Retrofit from trunk) Portal: Fixed friendlyname fields rendered as mandatory when they should have been readonly.
SVN:2.3[4659]
2017-04-04 08:03:22 +00:00
Denis Flaven
5712981b4e N°686 - protect the edition of dashboards against a no-longer-existing class.
SVN:2.3[4656]
2017-04-03 14:53:31 +00:00
Romain Quetiez
598965a51f (Retrofit from trunk) 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:2.3[4654]
2017-04-03 14:00:22 +00:00
Guillaume Lajarige
893725948a (Retrofit from trunk) Portal: Wrong form used in some inheritance cases.
SVN:2.3[4652]
2017-04-03 11:59:36 +00:00
Romain Quetiez
f2b4c17dca (Retrofit from trunk) 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:2.3[4650]
2017-04-01 20:07:44 +00:00
Guillaume Lajarige
a232681995 (Retrofit from trunk) DBObject::ExecActions : add_to_list action now accepts the source object id as first parameter
SVN:2.3[4648]
2017-03-31 15:20:17 +00:00
Romain Quetiez
e7e382e886 (Retrofit from trunk) 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:2.3[4645]
2017-03-31 14:49:55 +00:00
Romain Quetiez
b6702365c6 (Retrofit from trunk) 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:2.3[4643]
2017-03-31 14:36:22 +00:00
Guillaume Lajarige
2ce0bf4acc (Retrofit from trunk) N°800 Portal: log_kpi_duration / log_kpi_memory are now supported by the portal
SVN:2.3[4641]
2017-03-31 13:02:16 +00:00
Guillaume Lajarige
882ceff6fc (Retrofit from trunk) N°804 Portal: Object display crashed when a linkedset attribute has corrupted data (eg. an external key to 0)
SVN:2.3[4639]
2017-03-31 10:11:32 +00:00
Romain Quetiez
0b4597e65e (Retrofit from trunk) N.799 Setup failing (during database creation) with MetaEnum attribute having no mapping for the class they are declared in.
SVN:2.3[4637]
2017-03-30 19:26:02 +00:00
Romain Quetiez
60fa0e08f0 (Retrofit from trunk) 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:2.3[4635]
2017-03-29 20:00:00 +00:00
Romain Quetiez
64e0569613 (Retrofit from trunk) 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:2.3[4633]
2017-03-29 10:03:22 +00:00
Romain Quetiez
d0c1d94b14 (Retrofitted from trunk) N.780 Friendly name format ignored if only one attribute is used
SVN:2.3[4631]
2017-03-29 09:24:21 +00:00
Romain Quetiez
c15853d982 (Retrofit from trunk) 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:2.3[4629]
2017-03-29 08:29:57 +00:00
Romain Quetiez
1d0bf94ea4 (Retrofit from trunk) N.701 (continuation of [r4596] which introduced regressions on the handling of date fields)
SVN:2.3[4627]
2017-03-28 14:58:22 +00:00
Denis Flaven
e12f2501ee - Refactoring : structuration of the Exceptions thrown when the XML assembling fails
- Take into account the node specified as a parameter to saveXML()

SVN:2.3[4625]
2017-03-27 16:26:15 +00:00
Romain Quetiez
cd25a4dc34 (Retrofit from trunk) N.760 XSS vulnerability
SVN:2.3[4622]
2017-03-23 16:36:52 +00:00
Romain Quetiez
c0fd0e736f (Retrofit from trunk) N.519 REST/JSON Add a comment in pure text into the case log (only for add_item/items syntaxes, use format="text")
SVN:2.3[4620]
2017-03-23 15:18:13 +00:00
Romain Quetiez
2adcb108a5 (Retrofit from trunk) N.736 Ugly labels when hovering bar charts
SVN:2.3[4618]
2017-03-23 14:54:45 +00:00
Romain Quetiez
40fbdcc88c (Retrofit from trunk) 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:2.3[4616]
2017-03-23 09:11:09 +00:00
Romain Quetiez
76839b1b2c (Retrofit from trunk) 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) (note: this retrofit includes both commits made on trunk)
SVN:2.3[4613]
2017-03-22 21:01:44 +00:00
Guillaume Lajarige
05289dc241 (Retrofit from trunk) N°606 Portal: Request template fields marked as invalid when a mandatory textarea field was empty.
SVN:2.3[4606]
2017-03-21 09:55:46 +00:00
Denis Flaven
de81fbcb09 (retrofit from trunk) Make sure that the generated form field's IDs are valid ones.
SVN:2.3[4603]
2017-03-17 13:21:17 +00:00
Denis Flaven
602b64d19a (retrofit from trunk) More events from the property_field widgets to inform the enclosing form of the state changes.
SVN:2.3[4599]
2017-03-17 10:56:35 +00:00
Romain Quetiez
2a60a57c46 Retrofit from trunk - N.701 Data Synchro: dates can be reset by the mean of an empty string (still, integers and enums cannot be reset)
SVN:2.3[4597]
2017-03-17 09:16:23 +00:00
Romain Quetiez
b384313453 Retrofit from trunk - N.738 Setup not working if access_mode=2 and a synchro data source has a new attribute to create
SVN:2.3[4595]
2017-03-16 15:24:31 +00:00
Guillaume Lajarige
337a003d28 (Retrofit from trunk) N°772 Portal: Invalid URL in LinkedSet searchbox when editing an object (eg. Adding a Contact to an UserRequest)
SVN:2.3[4593]
2017-03-16 13:18:39 +00:00
Romain Quetiez
7df95addcc Retrofit from trunk - N.678 Data synchro: a line break or '<' in the description breaks the synchronized objects edition form.
SVN:2.3[4590]
2017-03-15 14:45:04 +00:00
Romain Quetiez
2055e9399f Retrofit from trunk - N.598 Custom fields with autocomplete failing if the subfield depends on another subfield
SVN:2.3[4586]
2017-03-14 15:22:18 +00:00
Romain Quetiez
dc7c622dba Retrofit from trunk - N.569 Enable the browser built-in spell checker for the rich text editor
SVN:2.3[4583]
2017-03-14 13:43:29 +00:00
Romain Quetiez
f42e641faf ReRetrofit from trunk - N.453 Emails coming from outlook. Many line breaks added when editing the ticket
SVN:2.3[4581]
2017-03-14 13:21:53 +00:00
Romain Quetiez
635ca103af Retrofit from trunk - N.757 Server log filled with warnings (ContextTag::Check)
SVN:2.3[4574]
2017-03-10 14:43:28 +00:00
Denis Flaven
4673a3f22d (retrofit from trunk) Bug fix: load modules before generating the WSDL file, since some modules may come with their own webservices.
SVN:2.3[4564]
2017-02-28 14:11:36 +00:00
Guillaume Lajarige
08cda15762 (Retrofit from trunk) 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:2.3[4562]
2017-02-28 09:51:23 +00:00
Denis Flaven
3c384ff498 Retrofit from trunk: Bug fix: protect against a non existing Contact class (a rather drastic iTop customization!)
SVN:2.3[4560]
2017-02-24 14:11:24 +00:00
Guillaume Lajarige
47587fb97e (Retrofit from trunk) N°620 - Fixed regression introduced in r4519: Portal: Url in notifications were broken since iTop 2.3.3.
SVN:2.3[4555]
2017-02-21 09:11:24 +00:00
Denis Flaven
f7f4fbce51 (retrofir from trunk) N° 615: spreadsheet export enhancement to remove unneeded line breaks.
SVN:2.3[4550]
2017-01-31 13:32:16 +00:00
Guillaume Lajarige
d898cffd4e (Retrofit from trunk) 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:2.3[4548]
2017-01-31 13:07:31 +00:00
Denis Flaven
9c89a58a57 Getting ready for the release of iTop 2.3.3.
SVN:2.3[4543]
2016-12-22 15:16:40 +00:00
Romain Quetiez
e7eecf81ee (Retrofit from trunk) 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:2.3[4539]
2016-12-19 16:11:46 +00:00
Guillaume Lajarige
b909dfc321 (Retrofit from trunk) N.551: Correcting spanish translations in the new portal (When creating a new user request in full ITIL or changing password)
SVN:2.3[4537]
2016-12-19 10:17:06 +00:00
Denis Flaven
c9ee203970 N. 549: "Font" menu broken in case logs (console and legacy portal). The menu was showing <> instead of font names.
SVN:2.3[4535]
2016-12-19 10:11:11 +00:00
Guillaume Lajarige
cbb771154a (Retrofit from trunk) Portal: Added german dictionnary
SVN:2.3[4533]
2016-12-16 09:17:11 +00:00
Romain Quetiez
cc74591036 (Retrofit from trunk) 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:2.3[4528]
2016-12-14 14:19:22 +00:00
Guillaume Lajarige
b6eeaae24a (Retrofit from trunk) Portal: Added itopversion to js/css file urls to prevent cache issues when upgrading.
SVN:2.3[4527]
2016-12-14 11:22:20 +00:00
Denis Flaven
f1966619b9 (Retrofit from trunk) Support of the "target" attribute for links.
SVN:2.3[4525]
2016-12-14 11:17:04 +00:00
Denis Flaven
01fa323b38 (Retrofit from trunk) Support of text-align in the styles.
SVN:2.3[4523]
2016-12-14 11:15:31 +00:00
Denis Flaven
6dca5afc83 (Retrofit from trunk) 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:2.3[4522]
2016-12-14 11:10:33 +00:00
Denis Flaven
aacdb525cf (Retrofit from trunk) 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:2.3[4520]
2016-12-13 16:19:25 +00:00
Romain Quetiez
864033f27c (Retrofit from trunk) 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:2.3[4518]
2016-12-13 14:26:29 +00:00
Denis Flaven
15f900e630 (retrofit from trunk) 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:2.3[4516]
2016-12-13 10:56:03 +00:00
Guillaume Lajarige
c0ba515797 (Retrofit from trunk) N.474: Support for "file" attribute (AttributeBlob) in the portal. READONLY only for now.
SVN:2.3[4513]
2016-12-13 10:18:00 +00:00
Guillaume Lajarige
dd0eec3cc8 (Retrofit from trunk) N.551: Added spanish translations to the enhanced portal thanks to the community :)
SVN:2.3[4509]
2016-12-08 13:22:53 +00:00
Romain Quetiez
437ace992e (Retrofit from trunk) N.545 HTML images not displayed when no login is required for the page.
SVN:2.3[4507]
2016-12-08 12:47:44 +00:00
Guillaume Lajarige
60ae969c89 (Retrofit from trunk) N.481: Portal: Impossible to submit a form with a duration attribute. Also fixed the displayed value in tables (ManageBrick and BrowseBrick)
SVN:2.3[4505]
2016-12-08 11:38:09 +00:00
Romain Quetiez
bc0645d5cc (Retrofit from trunk) N.490 Losing carrier returns and rich text formatting when the latest comments are copied to child tickets
SVN:2.3[4503]
2016-12-08 10:27:00 +00:00
Guillaume Lajarige
760454608d (Retrofit from trunk) N.546: Portal: Edit value in case log was kept after UserRequest update.
SVN:2.3[4502]
2016-12-08 10:26:07 +00:00
Romain Quetiez
75fcd2a021 (Retrofit from trunk) 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:2.3[4499]
2016-12-08 08:23:55 +00:00
Romain Quetiez
ff1f3c185b (Retrofit from trunk) 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) CI details)
SVN:2.3[4497]
2016-12-07 20:34:31 +00:00
Denis Flaven
a89252c6b3 (retrofit from trunk) N. 550 the OpCode cache may cause the upgrade of the datamodel to fail. Let's flush it after the compilation.
SVN:2.3[4495]
2016-12-07 17:37:22 +00:00
Romain Quetiez
d651196eae (Retrofit from trunk) 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:2.3[4493]
2016-12-05 12:54:32 +00:00
Romain Quetiez
73bedb1522 (Retrofit from trunk) Continuing [r4488] N.536 Regression introduced in [r4469] (N.505), itself fixing a regression introduced in [r4404]. REQUIRES TESTING
SVN:2.3[4491]
2016-12-05 10:00:27 +00:00
Romain Quetiez
b2f42ae3f4 (Retrofit from trunk) N.536 Regression introduced in [r4469] (N.505), itself fixing a regression introduced in [r4404]. REQUIRES TESTING
SVN:2.3[4489]
2016-12-02 20:43:17 +00:00
Romain Quetiez
44d3fb2738 (Retrofit from trunk) N.480 Page broken (missing menu + ...) when bulk modifying Document Notes (having various values)
SVN:2.3[4487]
2016-12-01 14:08:06 +00:00
Romain Quetiez
9a08895b2c (Retroffit from trunk) N.502 Too many backups on sundays
SVN:2.3[4484]
2016-12-01 09:53:15 +00:00
Romain Quetiez
3277be00c1 (Retrofit from trunk) N.527 Enable the template placeholders for AttributeCustomFields
SVN:2.3[4482]
2016-11-25 16:39:30 +00:00
Romain Quetiez
52e1a1d40a (Retrofit from trunk) N.520 Conflicts when upgrading modules in "extensions"
SVN:2.3[4480]
2016-11-22 09:00:22 +00:00
Romain Quetiez
89b4de01a9 (Retrofit from trunk) N.523 UserRights::ListProfiles must return an empty array if nobody is currently logged in (instead of a FATAL ERROR)
SVN:2.3[4479]
2016-11-18 15:49:03 +00:00
Denis Flaven
5d16ab9654 (retrofit from trunk): APCu comptability layer. Retrofit after validation.
SVN:2.3[4476]
2016-11-15 10:47:24 +00:00
Denis Flaven
61a006dfbe (retrofit from trunk) Support of non-case sensitive "forbidden values" in DesignerTextField
SVN:2.3[4474]
2016-11-04 16:19:15 +00:00
Romain Quetiez
e0f3cdac51 (Retroffit from trunk) 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:2.3[4470]
2016-10-28 09:10:22 +00:00
Romain Quetiez
4a6e08e3e9 N.504 Could not jump into the designer (APC, random)
SVN:2.3[4468]
2016-10-27 15:00:14 +00:00
Denis Flaven
d13270acc7 (retrofit from trunk) Bug fix: creating a new DOM Node containing the string "0" resulted in an empty node (no DOMText).
SVN:2.3[4465]
2016-10-27 08:30:49 +00:00
Denis Flaven
e6aafc165b Bug fix: creating a new DOM Node containing the string "0" resulted in an empty node (no DOMText).
SVN:2.3[4464]
2016-10-27 08:29:48 +00:00
Guillaume Lajarige
49f72aee28 (Retrofit from trunk) #497 Portal : Could not update object due to "Warning: preg_match(): Unknown modifier '/'"
SVN:2.3[4462]
2016-10-21 08:46:38 +00:00
Guillaume Lajarige
45f4d8f625 (Retrofit from trunk) #475 Portal : Could not upload attachments on IE9.
SVN:2.3[4458]
2016-10-14 09:58:09 +00:00
Romain Quetiez
3992425a27 (Retrofit from trunk) 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:2.3[4456]
2016-10-10 16:03:32 +00:00
Guillaume Lajarige
64ef7fbc08 (Retrofit from trunk) 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:2.3[4454]
2016-10-10 13:16:03 +00:00
Romain Quetiez
5807ae79d2 (Retrofit from trunk) 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 OQL parsing is impacted too. This retrofit include [r4448] and [r4451].
SVN:2.3[4452]
2016-10-10 09:03:45 +00:00
Guillaume Lajarige
41f77f63fd (Retrofit from trunk) Portal : Final touch to AllowedOrganizations by settings ignore_silos to true for ServiceFamily/Service/ServiceSubcategory on the default portal configuration
SVN:2.3[4450]
2016-10-07 09:40:39 +00:00
Guillaume Lajarige
67148bc80d (Retrofit from trunk) Allowed organizations Part II.
r4428
Portal : Allowed Organizations part for action rules.
---------------------
r4422
Removed debug traces for AllowAllData
---------------------



SVN:2.3[4447]
2016-10-06 07:14:59 +00:00
Guillaume Lajarige
28fa99d976 (Retrofit from trunk) #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:2.3[4446]
2016-10-06 07:13:33 +00:00
Guillaume Lajarige
e1c51d278e (Retrofit from trunk on bahalf of rquetiez) N°436 Core API: Correctly (mathematically!) handle the "allow all data" flag, with UNIONS and INTERSECTIONS. Requires testing
SVN:2.3[4445]
2016-10-06 07:12:02 +00:00
Guillaume Lajarige
85b38a07ee (Retrofit from trunk on bahalf of rquetiez) #1323 error.log polluted with the contents of each email sent (transport = PHPMail)
SVN:2.3[4444]
2016-10-06 07:10:51 +00:00
Guillaume Lajarige
c3c314097e (Retrofit from trunk) Allowed organizations Part I.
r4412
Portal : Missing AllDataAllowed
---------------------
r4411
Portal : Typo
---------------------
r4409
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 !
---------------------
r4406
Portal : Renamed <ignore_allowed_organizations> to <ignore_silos> for a more generic aproch
---------------------
r4405
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:2.3[4443]
2016-10-06 07:09:12 +00:00
Romain Quetiez
7d774c7c88 (Retrofit from trunk) N.444 ... fixing regression introduced in [r4438]
SVN:2.3[4442]
2016-10-04 13:17:32 +00:00
Romain Quetiez
bbcd1ef22c (Retrofit from trunk) 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:2.3[4439]
2016-10-03 11:50:55 +00:00
Guillaume Lajarige
fba368fb46 (Retrofit from trunk) Portal : Bug when adding item on the first LinkedSet of an edition form
SVN:2.3[4437]
2016-10-03 08:02:40 +00:00
Romain Quetiez
2a9a373c61 (Retrofit from trunk) 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:2.3[4434]
2016-09-30 14:18:24 +00:00
Guillaume Lajarige
42a882ae62 (Retrofit from trunk) Portal : Deadline attributes not displayed properly in ManageBrick
SVN:2.3[4432]
2016-09-30 12:51:49 +00:00
Guillaume Lajarige
634d96e23f (Retrofit from trunk) 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:2.3[4430]
2016-09-30 11:34:30 +00:00
Guillaume Lajarige
cc461630ea (Retrofit from trunk) Portal : ManageBrick crashing when displaying an abstract class with child classes attributes
SVN:2.3[4427]
2016-09-30 07:15:17 +00:00
Guillaume Lajarige
b3ca6f776e (Retrofit from trunk) Portal : Autocomplete bug with IE9 in forms
SVN:2.3[4425]
2016-09-29 10:17:44 +00:00
Romain Quetiez
dee911c12b (Retrofit from trunk) N.441 Character "à" in a case log causing the REST/JSON API to fail if mbstring is not enabled
SVN:2.3[4419]
2016-09-27 08:58:18 +00:00
Romain Quetiez
d7ee97f5a4 (Retrofit from trunk) Prerequisite for #1334. New API: DBObjectSet::SetOrderByClasses. Helper to sort on multicolumn queries (SELECT a, b FROM)
SVN:2.3[4414]
2016-09-23 15:31:18 +00:00
Guillaume Lajarige
67938e433b (Retrofit from trunk) Portal : Preserve debug parameter through urls
SVN:2.3[4410]
2016-09-22 09:31:38 +00:00
Guillaume Lajarige
01ef529db2 (Retrofit from trunk) Portal : Optimized column load in ManageBrick and BrowseBrick to improve performances
SVN:2.3[4407]
2016-09-21 14:17:26 +00:00
Romain Quetiez
bfcc6ea239 (Retrofit from trunk) #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:2.3[4403]
2016-09-15 10:03:41 +00:00
Romain Quetiez
a574b1b4e8 (Retrofit from trunk) #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:2.3[4400]
2016-09-14 15:54:33 +00:00
Denis Flaven
c9baf59018 (retrofit from trunk) 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:2.3[4395]
2016-09-12 13:00:13 +00:00
Romain Quetiez
49a1052333 (Retrofit from trunk) Fixes a regression introduced in [r4385] and causing a blank screen when editing an object
SVN:2.3[4391]
2016-09-06 13:51:15 +00:00
Denis Flaven
40006c3ba2 (Retrofit from trunk) (regression from iTop 2.2.x) ExternalFields were not automatically reloaded when the corresponding external key changed.
SVN:2.3[4389]
2016-09-06 10:09:27 +00:00
Romain Quetiez
b8e4f3d762 (Retrofit from trunk) Fixed XSS vulnerability
SVN:2.3[4387]
2016-09-06 10:06:22 +00:00
Romain Quetiez
a222f296ef (Retrofit from trunk) Rich text editor: the Maximize button icon is missing if iTop is installed in a directory which name contains spaces
SVN:2.3[4385]
2016-09-06 09:36:57 +00:00
Guillaume Lajarige
899045dece (Retrofit from trunk) Portal : Added Location scope to standard portal configuration because of the implementation of r4380
SVN:2.3[4383]
2016-09-06 09:31:17 +00:00
Guillaume Lajarige
096236cb3a (Retrofit from trunk) Portal : External keys OQL now intersect with scopes in forms!
SVN:2.3[4381]
2016-09-06 07:06:13 +00:00
Guillaume Lajarige
746c97818e (Retrofit from trunk) Portal : Added a new mode "apply_stimulus" for forms. This allows to add flags to attributes that are prompt during transitions.
SVN:2.3[4379]
2016-09-05 14:34:27 +00:00
Romain Quetiez
600c447529 #1321 Losing table borders (and other options configurable in the rich text editor)
SVN:2.3[4377]
2016-09-05 13:06:51 +00:00
Guillaume Lajarige
4f1be53b68 (Retrofit from trunk on behalf of rquetiez) Improved the comments for access_mode in the config file
SVN:2.3[4375]
2016-09-02 14:14:56 +00:00
Guillaume Lajarige
5e6061b341 (Retrofit from trunk) Portal : Request template OQL list fields marked as mandatory were not validated properly
SVN:2.3[4374]
2016-09-02 14:13:43 +00:00
Guillaume Lajarige
281edea101 (Retrofit from trunk) Portal : Updated inline documentation of UserProfile brick's <fields /> tag for easiest comprehension.
SVN:2.3[4373]
2016-09-02 14:12:39 +00:00
Romain Quetiez
8280159eee (Retrofit from trunk) 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:2.3[4372]
2016-09-02 12:56:21 +00:00
Guillaume Lajarige
c380c19d2a (Retrofit from trunk) Portal : Enhanced and refactored error feedback on ExternalKey / LinkedSet / CustomFields fields
SVN:2.3[4368]
2016-09-02 12:02:01 +00:00
Guillaume Lajarige
28ead17d00 (Retrofit from trunk) Portal : Request template list fields now have the autocomplete option.
SVN:2.3[4367]
2016-09-02 11:59:57 +00:00
Guillaume Lajarige
471bf9e820 (retrofit from trunk) Portal : Request template list fields now have the lookup/search option. (Autocomplete is still to be implemented!)
SVN:2.3[4366]
2016-09-02 11:58:18 +00:00
Guillaume Lajarige
cee84074e1 (Retrofit from trunk) 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:2.3[4365]
2016-09-01 10:30:53 +00:00
Guillaume Lajarige
70dbe00f4d (Retrofit from trunk) 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:2.3[4363]
2016-08-31 16:00:42 +00:00
Denis Flaven
3e81986e0f (retrofit from trunk) Bug fix: regression from 2.3.x: SOAP webservices were broken!
SVN:2.3[4361]
2016-08-31 14:57:41 +00:00
Denis Flaven
2cadb34eaa (retrofit from trunk) Enhancement: protect RenameValueInDB from non-existent attributes.
SVN:2.3[4358]
2016-08-30 12:58:08 +00:00
Denis Flaven
dc9a6382f9 (retrofit from trunk) #1297: timezone configuration setting was inoperant.
SVN:2.3[4356]
2016-08-29 12:52:51 +00:00
Guillaume Lajarige
40b3e8290b (Retrofit from trunk) Portal : Fixed a regression introduced by r4324 causing HTML entities on the browse brick.
SVN:2.3[4354]
2016-08-29 07:33:38 +00:00
Romain Quetiez
dfdec57d3f (Retrofit from trunk) CSV import failing with final class (localized value not taken into account)
SVN:2.3[4349]
2016-08-24 16:58:32 +00:00
Romain Quetiez
ce887e25bf (Retrofit from trunk) #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:2.3[4347]
2016-08-23 18:46:34 +00:00
Guillaume Lajarige
48d2e9213e (Retrofit from trunk) 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:2.3[4345]
2016-08-23 16:02:22 +00:00
Guillaume Lajarige
fb551cc3d2 (Retrofit from trunk) #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:2.3[4342]
2016-08-23 12:50:46 +00:00
Guillaume Lajarige
b76c890408 (Retrofit from trunk) #1281 Portal : Fixed a few hardcoded strings to dictionnaries
SVN:2.3[4341]
2016-08-23 12:47:20 +00:00
Guillaume Lajarige
8711356118 (Retrofit from trunk) Portal : Fixed a bug with external key as radio button in forms
SVN:2.3[4340]
2016-08-23 12:45:51 +00:00
Guillaume Lajarige
09aef4ef39 (Retrofit from trunk) 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:2.3[4339]
2016-08-23 12:44:38 +00:00
Guillaume Lajarige
950c868230 (Retrofit from trunk) #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:2.3[4338]
2016-08-23 12:41:56 +00:00
Romain Quetiez
93bbfeae1f (Retrofit from trunk) Portal : Removed console.log to prevent crashes on IE9
SVN:2.3[4337]
2016-08-23 12:36:01 +00:00
Guillaume Lajarige
7577e560bb (Retrofit from trunk) #1281 : Service catalog brick had 2 hardcoded headers ("Service" and "Sous-Service")
SVN:2.3[4331]
2016-08-11 10:07:25 +00:00
Denis Flaven
0f495e5730 (retrofit from trunk) #1279: 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:2.3[4329]
2016-08-11 09:50:20 +00:00
Denis Flaven
6ea6dcef16 (retrofit from trunk) Cleanup a Notice message: align the prototype of DBDeleteSingleObject to the current one.
SVN:2.3[4327]
2016-08-11 09:39:55 +00:00
Denis Flaven
5e2e6b393c (retrofit from trunk) Bug fix: support the display of HTML fields in the lists in the new portal.
SVN:2.3[4325]
2016-08-11 09:32:07 +00:00
Denis Flaven
593f1fadbe (retrofit from trunk) Cosmetics: Enlarge DateTime fields which were too narrow (the end of the time is not visible when editing).
SVN:2.3[4322]
2016-08-11 08:35:39 +00:00
Denis Flaven
43dd075c44 (retrofit from trunk) Regression introduced after 2.3.0-beta [r4217]: broken links to download / display blobs.
SVN:2.3[4320]
2016-08-10 15:55:31 +00:00
Denis Flaven
1632c51abd (retrofit from trunk) Performance optimization: do not load all the columns when it is not needed.
SVN:2.3[4318]
2016-08-10 14:58:59 +00:00
Denis Flaven
45c0ad5597 (retrofit from trunk) Image upload inside CKEditor (via drag and drop) seems to be desactivated on IE9.
SVN:2.3[4316]
2016-08-05 10:19:30 +00:00
Denis Flaven
c55a46e52b Remember that console.log breaks IE9 when the console is not open !!!
SVN:2.3[4314]
2016-08-05 10:07:09 +00:00
Denis Flaven
5863128c0c (Retrofit from trunk) Bug fix: properly disable the configuration editor in demo mode! (Regression)
SVN:2.3[4312]
2016-07-27 12:03:51 +00:00
Denis Flaven
10a9326e19 (Retrofit from trunk) Bug fix: properly disable the configuration editor in demo mode! (Regression)
SVN:2.3[4311]
2016-07-27 09:54:55 +00:00
Denis Flaven
eae396f250 Retrofit from trunk: 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
Increased version number of the portal-base module to 1.0.1 to reflect this change.

SVN:2.3[4309]
2016-07-26 08:18:42 +00:00
Romain Quetiez
33c5839273 Releasing 2.3.1
SVN:2.3[4304]
2016-07-08 12:19:26 +00:00
Denis Flaven
9f92bc4b8a (Retrofit) 2.3.0 Regression: login_mode was broken !
SVN:2.3[4303]
2016-07-08 12:05:59 +00:00
Romain Quetiez
2af2fd0aea Creating branch 2.3 (2.3.0 + missing czech translation for the enhanced customer portal)
SVN:2.3[4299]
2016-07-08 09:30:48 +00:00
419 changed files with 5358 additions and 7854 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

@@ -202,32 +202,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

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* Abstract class that implements some common and useful methods for displaying
* the objects
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -655,16 +655,19 @@ EOF
if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
{
// Check if the attribute is not read-only because of a synchro...
$aReasons = array();
$sSynchroIcon = '';
if ($iFlags & OPT_ATT_SLAVE)
{
$aReasons = array();
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" 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>";
}
$sTip = addslashes($sTip);
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
@@ -688,6 +691,7 @@ EOF
else
{
$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 'comments' => $sComments, 'infos' => $sInfos);
$aFieldsMap[$sAttCode] = $sInputId;
}
}
else
@@ -1744,7 +1748,7 @@ EOF
$aEventsList[] ='change';
$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"15\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"19\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
break;
case 'Duration':
@@ -1829,7 +1833,7 @@ EOF
$sStyle = 'style="'.implode('; ', $aStyles).'"';
}
$sHeader = '<div class="caselog_input_header"></div>'; // will be hidden in CSS (via :empty) if it remains empty
$sEditValue = $oAttDef->GetEditValue($value);
$sEditValue = is_object($value) ? $value->GetModifiedEntry('html') : '';
$sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : '';
$iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0;
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
@@ -3327,8 +3331,6 @@ EOF
{
$res = parent::DBInsertNoReload();
InlineImage::FinalizeInlineImages($this);
// Invoke extensions after insertion (the object must exist, have an id, etc.)
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
@@ -3338,6 +3340,14 @@ EOF
return $res;
}
/**
* Attaches InlineImages to the current object
*/
protected function OnObjectKeyReady()
{
InlineImage::FinalizeInlineImages($this);
}
protected function DBCloneTracked_Internal($newKey = null)
{
$oNewObj = parent::DBCloneTracked_Internal($newKey);
@@ -3502,18 +3512,21 @@ EOF
if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)))
{
// Check if the attribute is not read-only because of a synchro...
$aReasons = array();
$sSynchroIcon = '';
if ($iFlags & OPT_ATT_SLAVE)
{
$aReasons = array();
$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" 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>";
}
$sTip = addslashes($sTip);
$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
@@ -3542,7 +3555,6 @@ EOF
// b) or override some of the configuration settings, using the second parameter of ckeditor()
$aConfig = array();
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
$aConfig['font_style'] = $sLanguage;
$aConfig['language'] = $sLanguage;
$aConfig['contentsLanguage'] = $sLanguage;
$aConfig['extraPlugins'] = 'disabler';

View File

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

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
*/
@@ -795,9 +795,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 +813,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 +864,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 +960,6 @@ EOF
$sContextParam = $oContext->GetForLink();
$aGroupBy = array();
$aLabels = array();
$iTotalCount = 0;
$aValues = array();
$aURLs = array();
@@ -959,7 +967,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 +986,7 @@ EOF
$aNames = array();
foreach($aValues as $idx => $aValue)
{
$aNames[$idx] = $aValue['label_html'];
$aNames[$idx] = $aValue['label'];
}
$sJSNames = json_encode($aNames);
@@ -1006,6 +1013,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 +1070,7 @@ var chart = c3.generate({
},
legend: {
show: true,
position: 'right',
},
tooltip: {
format: {

View File

@@ -531,7 +531,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 +881,7 @@ class DesignerTextField extends DesignerFormField
$this->sValidationPattern = $sValidationPattern;
}
public function SetForbiddenValues($aValues, $sExplain)
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
{
$aForbiddenValues = $aValues;
@@ -893,7 +893,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')
@@ -1408,8 +1408,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

@@ -191,7 +191,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 +209,39 @@ 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
$(".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->m_sInitScript =
<<< EOF
try
@@ -444,8 +477,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()

View File

@@ -572,27 +572,59 @@ 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_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));
@@ -602,66 +634,33 @@ EOF
}
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

@@ -19,7 +19,7 @@
/**
* Class PortalWebPage
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @copyright Copyright (C) 2010-2016 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');

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-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
*/
@@ -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

@@ -163,38 +163,10 @@ class UILinksWidget
$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
});
PrepareWidgets();
EOF
);
}

View File

@@ -437,6 +437,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')
@@ -1275,10 +1314,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':

View File

@@ -138,6 +138,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);

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

@@ -0,0 +1,69 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
// 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);
}
}

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 @@
/**
* Typology for the attributes
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -565,7 +565,7 @@ abstract class AttributeDefinition
}
// - Comparing flags
if (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY))
if ($this->IsWritable() && (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY)))
{
$oFormField->SetMandatory(true);
}
@@ -2116,6 +2116,41 @@ class AttributeFinalClass extends AttributeString
}
}
/**
* An enum can be localized
*/
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
{
if ($bLocalizedValue)
{
// Lookup for the value matching the input
//
$sFoundValue = null;
$aRawValues = self::GetAllowedValues();
if (!is_null($aRawValues))
{
foreach ($aRawValues as $sKey => $sValue)
{
if ($sProposedValue == $sValue)
{
$sFoundValue = $sKey;
break;
}
}
}
if (is_null($sFoundValue))
{
return null;
}
return $this->MakeRealValue($sFoundValue, null);
}
else
{
return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier);
}
}
// Because this is sometimes used to get a localized/string version of an attribute...
public function GetEditValue($sValue, $oHostObj = null)
{
@@ -2733,7 +2768,7 @@ class AttributeCaseLog extends AttributeLongText
if ($proposedValue instanceof ormCaseLog)
{
// Passthrough
$ret = $proposedValue;
$ret = clone $proposedValue;
}
else
{
@@ -3551,8 +3586,16 @@ class AttributeMetaEnum extends AttributeEnum
else
{
$sParent = MetaModel::GetParentClass($sClass);
$aMappingData = $this->GetMapRule($sParent);
if (is_null($sParent))
{
$aMappingData = null;
}
else
{
$aMappingData = $this->GetMapRule($sParent);
}
}
return $aMappingData;
}
}
@@ -3690,6 +3733,15 @@ class AttributeDateTime extends AttributeDBField
}
protected function GetSQLCol($bFullSpec = false) {return "DATETIME";}
public function GetImportColumns()
{
// Allow an empty string to be a valid value (synonym for "reset")
$aColumns = array();
$aColumns[$this->GetCode()] = 'VARCHAR(19)';
return $aColumns;
}
public static function GetAsUnixSeconds($value)
{
$oDeadlineDateTime = new DateTime($value);
@@ -3791,6 +3843,8 @@ class AttributeDateTime extends AttributeDBField
elseif (empty($value))
{
// Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null.
// todo: this is NOT valid in strict mode (default setting for MySQL 5.7)
// todo: if to be kept, this should be overloaded for AttributeDate (0000-00-00)
return '0000-00-00 00:00:00';
}
return $value;
@@ -4003,7 +4057,7 @@ class AttributeDuration extends AttributeInteger
static public function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\LabelField';
return '\\Combodo\\iTop\\Form\\Field\\DurationField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
@@ -4017,7 +4071,7 @@ class AttributeDuration extends AttributeInteger
// Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition
$sAttCode = $this->GetCode();
$oFormField->SetCurrentValue(html_entity_decode($oObject->GetAsHTML($sAttCode), ENT_QUOTES, 'UTF-8'));
$oFormField->SetCurrentValue($oObject->Get($sAttCode));
$oFormField->SetReadOnly(true);
return $oFormField;
@@ -4073,7 +4127,15 @@ class AttributeDate extends AttributeDateTime
public function GetEditClass() {return "Date";}
protected function GetSQLCol($bFullSpec = false) {return "DATE";}
public function GetImportColumns()
{
// Allow an empty string to be a valid value (synonym for "reset")
$aColumns = array();
$aColumns[$this->GetCode()] = 'VARCHAR(10)';
return $aColumns;
}
/**
* Override to specify Field class
*
@@ -4330,9 +4392,13 @@ class AttributeExternalKey extends AttributeDBFieldVoid
$oTmpField = $oFormField;
$oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject)
{
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
$oSearch->SetInternalParams(array('this' => $oObject));
$oTmpField->SetSearch($oSearch);
// We set search object only if it has not already been set (overrided)
if ($oTmpField->GetSearch() === null)
{
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
$oSearch->SetInternalParams(array('this' => $oObject));
$oTmpField->SetSearch($oSearch);
}
});
}
else
@@ -4825,7 +4891,11 @@ class AttributeBlob extends AttributeDefinition
// from a CSV by specifying its path/URL
public function MakeRealValue($proposedValue, $oHostObj)
{
if (!is_object($proposedValue))
if (is_object($proposedValue))
{
$proposedValue = clone $proposedValue;
}
else
{
if (file_exists($proposedValue) && UserRights::IsAdministrator())
{
@@ -5063,6 +5133,33 @@ class AttributeBlob extends AttributeDefinition
}
return $sFingerprint;
}
static public function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\BlobField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
// Note: As of today we want this field to always be read-only
$oFormField->SetReadOnly(true);
// Generating urls
$value = $oObject->Get($this->GetCode());
$oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
$oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
}
/**
@@ -5078,7 +5175,11 @@ class AttributeImage extends AttributeBlob
// from a CSV by specifying its path/URL
public function MakeRealValue($proposedValue, $oHostObj)
{
if (!is_object($proposedValue))
if (is_object($proposedValue))
{
$proposedValue = clone $proposedValue;
}
else
{
if (file_exists($proposedValue) && UserRights::IsAdministrator())
{
@@ -5975,7 +6076,11 @@ class AttributeOneWayPassword extends AttributeDefinition
public function MakeRealValue($proposedValue, $oHostObj)
{
$oPassword = $proposedValue;
if (!is_object($oPassword))
if (is_object($oPassword))
{
$oPassword = clone $proposedValue;
}
else
{
$oPassword = new ormPassword('', '');
$oPassword->SetPassword($proposedValue);

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

View File

@@ -828,8 +828,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'));
}

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.
//
@@ -48,7 +48,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);
@@ -246,7 +245,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' => '',
@@ -990,7 +989,6 @@ class Config
protected $m_bLogNotification;
protected $m_bLogIssue;
protected $m_bLogWebService;
protected $m_bLogQueries; // private setting
protected $m_bQueryCacheEnabled; // private setting
/**
@@ -1085,7 +1083,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 +1195,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 +1313,7 @@ class Config
public function GetLogQueries()
{
return $this->m_bLogQueries;
return false;
}
public function GetQueryCacheEnabled()
@@ -1519,7 +1515,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 +1573,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

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

@@ -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);
@@ -1407,6 +1411,12 @@ 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()
{
@@ -1644,7 +1654,9 @@ abstract class DBObject implements iDisplay
$this->DBInsertSingleTable($sParentClass);
}
$this->DBWriteLinks();
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->m_bIsInDB = true;
@@ -1931,6 +1943,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);
@@ -3393,7 +3406,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);
}

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,10 +20,12 @@
/**
* Define filters for a given class of objects (formerly named "filter")
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Dev hack for disabling the some query build optimizations (Folding/Merging)
define('ENABLE_OPT', true);
class DBObjectSearch extends DBSearch
{
@@ -35,6 +37,11 @@ class DBObjectSearch extends DBSearch
private $m_aPointingTo;
private $m_aReferencedBy;
// By default, some information may be hidden to the current user
// But it may happen that we need to disable that feature
protected $m_bAllowAllData = false;
protected $m_bDataFiltered = false;
public function __construct($sClass, $sClassAlias = null)
{
parent::__construct();
@@ -52,6 +59,11 @@ class DBObjectSearch extends DBSearch
$this->m_aReferencedBy = array();
}
public function AllowAllData($bAllowAllData = true) {$this->m_bAllowAllData = $bAllowAllData;}
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
protected function IsDataFiltered() {return $this->m_bDataFiltered; }
protected function SetDataFiltered() {$this->m_bDataFiltered = true;}
// Create a search definition that leads to 0 result, still a valid search object
static public function FromEmptySet($sClass)
{
@@ -172,12 +184,87 @@ class DBObjectSearch extends DBSearch
{
if (!array_key_exists($sAlias, $this->m_aClasses))
{
throw new CoreException("Invalid class alias $sAlias");
throw new CoreException("SetSelectedClasses: Invalid class alias $sAlias");
}
$this->m_aSelectedClasses[$sAlias] = $this->m_aClasses[$sAlias];
}
}
/**
* 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)
{
$bFound = false;
if (array_key_exists($sOldName, $this->m_aClasses))
{
$bFound = true;
}
if (array_key_exists($sNewName, $this->m_aClasses))
{
throw new Exception("RenameAlias: alias '$sNewName' already used");
}
$aClasses = array();
foreach ($this->m_aClasses as $sAlias => $sClass)
{
if ($sAlias === $sOldName)
{
$aClasses[$sNewName] = $sClass;
}
else
{
$aClasses[$sAlias] = $sClass;
}
}
$this->m_aClasses = $aClasses;
$aSelectedClasses = array();
foreach ($this->m_aSelectedClasses as $sAlias => $sClass)
{
if ($sAlias === $sOldName)
{
$aSelectedClasses[$sNewName] = $sClass;
}
else
{
$aSelectedClasses[$sAlias] = $sClass;
}
}
$this->m_aSelectedClasses = $aSelectedClasses;
$this->m_oSearchCondition->RenameAlias($sOldName, $sNewName);
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $oExtFilter)
{
$bFound = $oExtFilter->RenameAlias($sOldName, $sNewName) || $bFound;
}
}
}
foreach($this->m_aReferencedBy as $sForeignClass => $aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $oForeignFilter)
{
$bFound = $oForeignFilter->RenameAlias($sOldName, $sNewName) || $bFound;
}
}
}
}
return $bFound;
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
@@ -554,8 +641,85 @@ class DBObjectSearch extends DBSearch
return null;
}
/**
* Helper to
* - convert a translation table (format optimized for the translation in an expression tree) into simple hash
* - compile over an eventually existing map
*
* @param $aRealiasingMap Map to update
* @param $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
* @return array of <old-alias> => <new-alias>
*/
protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
{
if ($aRealiasingMap !== null)
{
foreach ($aAliasTranslation as $sPrevAlias => $aRules)
{
if (isset($aRules['*']))
{
$sNewAlias = $aRules['*'];
$sOriginalAlias = array_search($sPrevAlias, $aRealiasingMap);
if ($sOriginalAlias !== false)
{
$aRealiasingMap[$sOriginalAlias] = $sNewAlias;
}
else
{
$aRealiasingMap[$sPrevAlias] = $sNewAlias;
}
}
}
}
}
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
/**
* Completes the list of alias=>class by browsing the whole structure recursively
* This a workaround to handle some cases in which the list of classes is not correctly updated.
* This code should disappear as soon as DBObjectSearch get split between a container search class and a Node class
*
* @param $aClasses List to be completed
*/
protected function RecomputeClassList(&$aClasses)
{
$aClasses[$this->GetFirstJoinedClassAlias()] = $this->GetFirstJoinedClass();
// Recurse in the query tree
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
{
foreach($aPointingTo as $iOperatorCode => $aFilter)
{
foreach($aFilter as $oFilter)
{
$oFilter->RecomputeClassList($aClasses);
}
}
}
foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
{
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
{
foreach ($aFilters as $oForeignFilter)
{
$oForeignFilter->RecomputeClassList($aClasses);
}
}
}
}
}
/**
* @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)
{
if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode))
{
@@ -579,6 +743,28 @@ class DBObjectSearch extends DBSearch
$aAliasTranslation = array();
$res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass()))
{
if (isset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]))
{
foreach ($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode] as $oRemoteFilter)
{
if ($this->GetClass() == $oRemoteFilter->GetClass())
{
// Optimization - fold sibling query
$aAliasTranslation = array();
$this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation);
unset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]);
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false);
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
break;
}
}
}
}
$this->RecomputeClassList($this->m_aClasses);
return $res;
}
@@ -587,11 +773,33 @@ class DBObjectSearch extends DBSearch
// Find the node on which the new tree must be attached (most of the time it is "this")
$oReceivingFilter = $this->GetNode($this->GetClassAlias());
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
$oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter;
$bMerged = false;
if (ENABLE_OPT && isset($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode]))
{
foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting)
{
if ($oExisting->GetClass() == $oFilter->GetClass())
{
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
$bMerged = true;
break;
}
}
}
if (!$bMerged)
{
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
$oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter;
}
}
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)
{
$sForeignClass = $oFilter->GetClass();
if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode))
@@ -604,6 +812,7 @@ class DBObjectSearch extends DBSearch
// à refaire en spécifique dans FromOQL
throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
}
// Note: though it seems to be a good practice to clone the given source filter
// (as it was done and fixed an issue in Intersect())
// this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge)
@@ -613,6 +822,28 @@ class DBObjectSearch extends DBSearch
$aAliasTranslation = array();
$res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass()))
{
if (isset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]))
{
foreach ($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode] as $oRemoteFilter)
{
if ($this->GetClass() == $oRemoteFilter->GetClass())
{
// Optimization - fold sibling query
$aAliasTranslation = array();
$this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation);
unset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]);
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false);
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
break;
}
}
}
}
$this->RecomputeClassList($this->m_aClasses);
return $res;
}
@@ -623,8 +854,24 @@ class DBObjectSearch extends DBSearch
// Find the node on which the new tree must be attached (most of the time it is "this")
$oReceivingFilter = $this->GetNode($this->GetClassAlias());
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
$oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter;
$bMerged = false;
if (ENABLE_OPT && isset($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode]))
{
foreach ($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode] as $oExisting)
{
if ($oExisting->GetClass() == $oFilter->GetClass())
{
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
$bMerged = true;
break;
}
}
}
if (!$bMerged)
{
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
$oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter;
}
}
public function Intersect(DBSearch $oFilter)
@@ -654,7 +901,10 @@ class DBObjectSearch extends DBSearch
$oLeftFilter = $this->DeepClone();
$oRightFilter = $oRightFilter->DeepClone();
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
$oLeftFilter->AllowAllData($bAllowAllData);
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
{
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
@@ -810,7 +1060,7 @@ class DBObjectSearch extends DBSearch
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
}
public function ToOQL($bDevelopParams = false, $aContextParams = null)
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
{
// Currently unused, but could be useful later
$bRetrofitParams = false;
@@ -850,6 +1100,10 @@ class DBObjectSearch extends DBSearch
{
$sRes .= " AND MATCHES '$sFullText'";
}
if ($bWithAllowAllFlag && $this->m_bAllowAllData)
{
$sRes .= " ALLOW ALL DATA";
}
return $sRes;
}
@@ -1007,6 +1261,22 @@ class DBObjectSearch extends DBSearch
$aAliases = array($sClassAlias => $sClass);
// Note: the condition must be built here, it may be altered later on when optimizing some joins
$oConditionTree = $oOqlQuery->GetCondition();
if ($oConditionTree instanceof Expression)
{
$aRawAliases = array($sClassAlias => $sClass);
$aJoinSpecs = $oOqlQuery->GetJoins();
if (is_array($aJoinSpecs))
{
foreach ($aJoinSpecs as $oJoinSpec)
{
$aRawAliases[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
}
}
$this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aRawAliases);
}
// Maintain an array of filters, because the flat list is in fact referring to a tree
// And this will be an easy way to dispatch the conditions
// $this will be referenced by the other filters, or the other way around...
@@ -1015,19 +1285,32 @@ class DBObjectSearch extends DBSearch
$aJoinSpecs = $oOqlQuery->GetJoins();
if (is_array($aJoinSpecs))
{
$aAliasTranslation = array();
foreach ($aJoinSpecs as $oJoinSpec)
{
$sJoinClass = $oJoinSpec->GetClass();
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
if (isset($aAliasTranslation[$sJoinClassAlias]['*']))
{
$sJoinClassAlias = $aAliasTranslation[$sJoinClassAlias]['*'];
}
// Assumption: ext key on the left only !!!
// normalization should take care of this
$oLeftField = $oJoinSpec->GetLeftField();
$sFromClass = $oLeftField->GetParent();
if (isset($aAliasTranslation[$sFromClass]['*']))
{
$sFromClass = $aAliasTranslation[$sFromClass]['*'];
}
$sExtKeyAttCode = $oLeftField->GetName();
$oRightField = $oJoinSpec->GetRightField();
$sToClass = $oRightField->GetParent();
if (isset($aAliasTranslation[$sToClass]['*']))
{
$sToClass = $aAliasTranslation[$sToClass]['*'];
}
$aAliases[$sJoinClassAlias] = $sJoinClass;
$aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias);
@@ -1069,19 +1352,16 @@ class DBObjectSearch extends DBSearch
{
$oReceiver = $aJoinItems[$sToClass];
$oNewComer = $aJoinItems[$sFromClass];
$aAliasTranslation = array();
$oReceiver->AddCondition_ReferencedBy_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode);
}
else
{
$oReceiver = $aJoinItems[$sFromClass];
$oNewComer = $aJoinItems[$sToClass];
$aAliasTranslation = array();
$oReceiver->AddCondition_PointingTo_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode);
}
}
$this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false /* leave unresolved fields */);
}
// Check and prepare the select information
@@ -1092,12 +1372,6 @@ class DBObjectSearch extends DBSearch
$this->m_aSelectedClasses[$sClassToSelect] = $aAliases[$sClassToSelect];
}
$this->m_aClasses = $aAliases;
$oConditionTree = $oOqlQuery->GetCondition();
if ($oConditionTree instanceof Expression)
{
$this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
}
}
////////////////////////////////////////////////////////////////////////////
@@ -1129,7 +1403,139 @@ class DBObjectSearch extends DBSearch
return $oSQLQuery->RenderUpdate($aScalarArgs);
}
public function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
{
// Hide objects that are not visible to the current user
//
$oSearch = $this;
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
{
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false)
{
// Make sure this is a valid search object, saying NO for all
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
}
if (is_object($oVisibleObjects))
{
$oVisibleObjects->AllowAllData();
$oSearch = $this->Intersect($oVisibleObjects);
$oSearch->SetDataFiltered();
}
else
{
// should be true at this point, meaning that no additional filtering
// is required
}
}
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
//
$aModifierProperties = MetaModel::MakeModifierProperties($oSearch);
// Create a unique cache id
//
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
{
// Need to identify the query
$sOqlQuery = $oSearch->ToOql(false, null, true);
if (count($aModifierProperties))
{
array_multisort($aModifierProperties);
$sModifierProperties = json_encode($aModifierProperties);
}
else
{
$sModifierProperties = '';
}
$sRawId = $sOqlQuery.$sModifierProperties;
if (!is_null($aAttToLoad))
{
$sRawId .= json_encode($aAttToLoad);
}
if (!is_null($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oExpr)
{
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
}
}
$sRawId .= $bGetCount;
if (is_array($aSelectedClasses))
{
$sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns
}
$sOqlId = md5($sRawId);
}
else
{
$sOqlQuery = "SELECTING... ".$oSearch->GetClass();
$sOqlId = "query id ? n/a";
}
// Query caching
//
if (self::$m_bQueryCacheEnabled)
{
// Warning: using directly the query string as the key to the hash array can FAIL if the string
// is long and the differences are only near the end... so it's safer (but not bullet proof?)
// to use a hash (like md5) of the string as the key !
//
// Example of two queries that were found as similar by the hash array:
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2
// and
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2
// the only difference is R instead or O at position 285 (TTR instead of TTO)...
//
if (array_key_exists($sOqlId, self::$m_aQueryStructCache))
{
// hit!
$oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId]));
// Note: cloning is not enough because the subtree is made of objects
}
elseif (self::$m_bUseAPCCache)
{
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
$sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId;
$oKPI = new ExecutionKPI();
$result = apc_fetch($sOqlAPCCacheId);
$oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery);
if (is_object($result))
{
$oSQLQuery = $result;
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery;
}
}
}
if (!isset($oSQLQuery))
{
$oKPI = new ExecutionKPI();
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
if (self::$m_bQueryCacheEnabled)
{
if (self::$m_bUseAPCCache)
{
$oKPI = new ExecutionKPI();
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
}
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone();
}
}
return $oSQLQuery;
}
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
{
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);

View File

@@ -445,8 +445,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 +461,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
*

View File

@@ -42,14 +42,9 @@ abstract class DBSearch
const JOIN_POINTING_TO = 0;
const JOIN_REFERENCED_BY = 1;
protected $m_bDataFiltered = false;
protected $m_bNoContextParameters = false;
protected $m_aModifierProperties = array();
// By default, some information may be hidden to the current user
// But it may happen that we need to disable that feature
protected $m_bAllowAllData = false;
public function __construct()
{
}
@@ -62,12 +57,11 @@ abstract class DBSearch
return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well
}
public function AllowAllData() {$this->m_bAllowAllData = true;}
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
abstract public function AllowAllData();
abstract public function IsAllDataAllowed();
public function NoContextParameters() {$this->m_bNoContextParameters = true;}
public function HasContextParameters() {return $this->m_bNoContextParameters;}
public function IsDataFiltered() {return $this->m_bDataFiltered; }
public function SetDataFiltered() {$this->m_bDataFiltered = true;}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
@@ -103,6 +97,15 @@ abstract class DBSearch
*/
abstract public function SetSelectedClasses($aSelectedClasses);
/**
* Change any alias of the query tree
*
* @param $sOldName
* @param $sNewName
* @return bool True if the alias has been found and changed
*/
abstract public function RenameAlias($sOldName, $sNewName);
abstract public function IsAny();
public function Describe(){return 'deprecated - use ToOQL() instead';}
@@ -127,19 +130,35 @@ abstract class DBSearch
abstract public function AddConditionAdvanced($sAttSpec, $value);
abstract public function AddCondition_FullText($sFullText);
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS);
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $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
*/
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
/**
* @param DBObjectSearch $oFilter
* @param $sForeignExtKeyAttCode
* @param int $iOperatorCode
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
*/
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
abstract public function Intersect(DBSearch $oFilter);
/**
*
* @param DBSearch $oFilter
* @param integer $iDirection
* @param string $sExtKeyAttCode
* @param integer $iOperatorCode
* @param array &$RealisasingMap Map of aliases from the attached query, that could have been renamed by the optimization process
* @return DBSearch
*/
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
{
$oSourceFilter = $this->DeepClone();
$oRet = null;
@@ -149,7 +168,7 @@ abstract class DBSearch
$aSearches = array();
foreach ($oFilter->GetSearches() as $oSearch)
{
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode);
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
}
$oRet = new DBUnionSearch($aSearches);
}
@@ -157,7 +176,7 @@ abstract class DBSearch
{
if ($iDirection === static::JOIN_POINTING_TO)
{
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
}
else
{
@@ -165,7 +184,7 @@ abstract class DBSearch
{
throw new Exception('Only TREE_OPERATOR_EQUALS operator code is supported yet for AddCondition_ReferencedBy.');
}
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode);
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode, TREE_OPERATOR_EQUALS, $aRealiasingMap);
}
$oRet = $oSourceFilter;
}
@@ -219,7 +238,7 @@ abstract class DBSearch
return $oSearchWithAlias;
}
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null);
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false);
static protected $m_aOQLQueries = array();
@@ -492,129 +511,8 @@ abstract class DBSearch
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
{
// Hide objects that are not visible to the current user
//
$oSearch = $this;
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
{
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false)
{
// Make sure this is a valid search object, saying NO for all
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
}
if (is_object($oVisibleObjects))
{
$oSearch = $this->Intersect($oVisibleObjects);
$oSearch->SetDataFiltered();
}
else
{
// should be true at this point, meaning that no additional filtering
// is required
}
}
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
//
$aModifierProperties = MetaModel::MakeModifierProperties($oSearch);
// Create a unique cache id
//
if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
{
// Need to identify the query
$sOqlQuery = $oSearch->ToOql();
if (count($aModifierProperties))
{
array_multisort($aModifierProperties);
$sModifierProperties = json_encode($aModifierProperties);
}
else
{
$sModifierProperties = '';
}
$sRawId = $sOqlQuery.$sModifierProperties;
if (!is_null($aAttToLoad))
{
$sRawId .= json_encode($aAttToLoad);
}
if (!is_null($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oExpr)
{
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
}
}
$sRawId .= $bGetCount;
$sOqlId = md5($sRawId);
}
else
{
$sOqlQuery = "SELECTING... ".$oSearch->GetClass();
$sOqlId = "query id ? n/a";
}
// Query caching
//
if (self::$m_bQueryCacheEnabled)
{
// Warning: using directly the query string as the key to the hash array can FAIL if the string
// is long and the differences are only near the end... so it's safer (but not bullet proof?)
// to use a hash (like md5) of the string as the key !
//
// Example of two queries that were found as similar by the hash array:
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2
// and
// SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2
// the only difference is R instead or O at position 285 (TTR instead of TTO)...
//
if (array_key_exists($sOqlId, self::$m_aQueryStructCache))
{
// hit!
$oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId]));
// Note: cloning is not enough because the subtree is made of objects
}
elseif (self::$m_bUseAPCCache)
{
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
$sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId;
$oKPI = new ExecutionKPI();
$result = apc_fetch($sOqlAPCCacheId);
$oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery);
if (is_object($result))
{
$oSQLQuery = $result;
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery;
}
}
}
if (!isset($oSQLQuery))
{
$oKPI = new ExecutionKPI();
$oSQLQuery = $oSearch->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
$oSQLQuery->SetSourceOQL($sOqlQuery);
$oKPI->ComputeStats('MakeSQLQuery', $sOqlQuery);
if (self::$m_bQueryCacheEnabled)
{
if (self::$m_bUseAPCCache)
{
$oKPI = new ExecutionKPI();
apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL);
$oKPI->ComputeStats('Query APC (store)', $sOqlQuery);
}
self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone();
}
}
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
$oSQLQuery->SetSourceOQL($this->ToOQL());
// Join to an additional table, if required...
//
@@ -624,7 +522,7 @@ abstract class DBSearch
$aExtendedFields = array();
foreach($aExtendedDataSpec['fields'] as $sColumn)
{
$sColRef = $oSearch->GetClassAlias().'_extdata_'.$sColumn;
$sColRef = $this->GetClassAlias().'_extdata_'.$sColumn;
$aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias);
}
$oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields);

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,22 @@ 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;
}
/**
* Find the lowest common ancestor for each of the selected class
*/
@@ -187,6 +203,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 +315,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 +404,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 +456,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)
protected 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();
@@ -468,7 +515,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

@@ -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,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,7 +154,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
protected static $aTagsWhiteList = array(
'html' => array(),
'body' => array(),
'a' => array('href', 'name', 'style'),
'a' => array('href', 'name', 'style', 'target'),
'p' => array('style'),
'br' => array(),
'span' => array('style'),
@@ -159,7 +175,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 +199,37 @@ 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()
{
if (!array_key_exists('href', self::$aAttrsWhiteList))
{
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/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

@@ -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;
}
?>

View File

@@ -22,6 +22,7 @@ require_once(APPROOT.'core/querymodifier.class.inc.php');
require_once(APPROOT.'core/metamodelmodifier.inc.php');
require_once(APPROOT.'core/computing.inc.php');
require_once(APPROOT.'core/relationgraph.class.inc.php');
require_once(APPROOT.'core/apc-compat.php');
/**
* Metamodel
@@ -1779,7 +1780,7 @@ abstract class MetaModel
$oFriendlyName = new AttributeFriendlyName($sFriendlyNameAttCode, $sAttCode);
$oFriendlyName->SetHostClass($sClass);
self::$m_aAttribDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyName;
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;
self::$m_aAttribOrigins[$sClass][$sFriendlyNameAttCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
$oFriendlyNameFlt = new FilterFromAttribute($oFriendlyName);
self::$m_aFilterDefs[$sClass][$sFriendlyNameAttCode] = $oFriendlyNameFlt;
self::$m_aFilterOrigins[$sClass][$sFriendlyNameAttCode] = $sRemoteClass;

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 {
@@ -388,8 +388,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--)
{
@@ -568,8 +569,6 @@ class ormCaseLog {
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
$sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
if (isset($oJson->user_id))
{
if (!UserRights::IsAdministrator())
@@ -616,10 +615,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 +644,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

@@ -115,21 +115,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 +184,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

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

@@ -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();
}
@@ -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)
@@ -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,8 +449,8 @@ 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))
@@ -540,10 +540,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;
}
}
}
}

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,6 +157,18 @@ 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);
@@ -175,7 +197,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 +252,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 +281,69 @@ 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>";
}
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 +391,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
*/
@@ -138,6 +138,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;

View File

@@ -245,7 +245,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'));
}
@@ -1045,7 +1045,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))

View File

@@ -292,13 +292,13 @@ td a.mailto, td a.mailto:visited {
text-decoration: none;
color: #000;
padding-left: 20px;
background: url(../images/mail.png?v=v2.3.0b) no-repeat left;
background: url(../images/mail.png?v=v2.3.0) no-repeat left;
}
td a.mailto:hover {
text-decoration: underline;
color: #e87c1e;
padding-left: 20px;
background: url(../images/mail.png?v=v2.3.0b) no-repeat left;
background: url(../images/mail.png?v=v2.3.0) no-repeat left;
}
a.small_action {
font-family: Tahoma, Verdana, Arial, Helvetica;
@@ -316,10 +316,10 @@ a.small_action {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
background: #e87c1e url(../images/actions_left.png?v=v2.3.0b) no-repeat left;
background: #e87c1e url(../images/actions_left.png?v=v2.3.0) no-repeat left;
}
.actions_details span {
background: url(../images/actions_right.png?v=v2.3.0b) no-repeat right;
background: url(../images/actions_right.png?v=v2.3.0) no-repeat right;
color: #fff;
font-weight: bold;
padding-top: 2px;
@@ -493,7 +493,7 @@ div.actions_menu > ul {
nowidth: 70px;
padding-left: 5px;
/* Nasty work-around for IE... en attendant mieux */
background: #e87c1e url(../images/actions_left.png?v=v2.3.0b) no-repeat top left;
background: #e87c1e url(../images/actions_left.png?v=v2.3.0) no-repeat top left;
cursor: pointer;
margin: 0;
}
@@ -505,7 +505,7 @@ div.actions_menu > ul > li {
height: 17px;
padding-right: 16px;
padding-left: 4px;
background: url(../images/actions_right.png?v=v2.3.0b) no-repeat top right transparent;
background: url(../images/actions_right.png?v=v2.3.0) no-repeat top right transparent;
font-weight: bold;
color: #fff;
vertical-align: middle;
@@ -648,7 +648,7 @@ td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-da
display: block;
text-indent: -2000px;
overflow: hidden;
background: url(../images/calendar.png?v=v2.3.0b) no-repeat;
background: url(../images/calendar.png?v=v2.3.0) no-repeat;
}
td a.dp-choose-date.dp-disabled, a.dp-choose-date.dp-disabled {
background-position: 0 -20px;
@@ -739,19 +739,19 @@ div.HRDrawer {
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.odd td.truncated, table.listResults tr td.truncated, .wizContainer table.listResults tr.odd td.truncated, .wizContainer table.listResults tr td.truncated {
background: url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
background: url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.even td.truncated, .wizContainer table.listResults tr.even td.truncated {
background: #f9f9f1 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
background: #f9f9f1 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.even td.hover.truncated, .wizContainer table.listResults tr.even td.hover.truncated {
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.odd td.hover.truncated, table.listResults tr td.hover.truncated, .wizContainer table.listResults tr.odd td.hover.truncated, .wizContainer table.listResults tr td.hover.truncated {
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0b) bottom repeat-x;
background: #fdf5d0 url(../images/truncated.png?v=v2.3.0) bottom repeat-x;
}
table.listResults.truncated {
border-bottom: 0;
@@ -859,7 +859,7 @@ div#logo {
div#logo div {
height: 88px;
width: 244px;
background: url(../images/itop-logo-2.png?v=v2.3.0b) left no-repeat;
background: url(../images/itop-logo-2.png?v=v2.3.0) left no-repeat;
}
#left-pane .ui-layout-north {
overflow: hidden;
@@ -908,7 +908,7 @@ div#logo div {
}
#global-search-image {
vertical-align: middle;
background: url(../images/search.png?v=v2.3.0b) center center no-repeat;
background: url(../images/search.png?v=v2.3.0) center center no-repeat;
display: inline-block;
width: 28px;
height: 30px;
@@ -937,7 +937,7 @@ span.ui-icon {
margin: 0 2px;
}
.ui-layout-button-pin-down {
background: url(../images/splitter-bkg.png?v=v2.3.0b) transparent;
background: url(../images/splitter-bkg.png?v=v2.3.0) transparent;
width: 16px;
background-position: -144px -144px;
}
@@ -1148,7 +1148,7 @@ img.prev, img.first, img.next, img.last {
}
div.actions_button {
float: right;
background: #e87c1e url("../images/actions_left.png?v=v2.3.0b") no-repeat scroll left top;
background: #e87c1e url("../images/actions_left.png?v=v2.3.0") no-repeat scroll left top;
padding-left: 5px;
margin-top: 0;
margin-right: 10px;
@@ -1156,7 +1156,7 @@ div.actions_button {
vertical-align: middle;
}
div.actions_button a, .actions_button a:hover, .actions_button a:visited {
background: #e87c1e url(../images/actions_bkg.png?v=v2.3.0b) no-repeat scroll right top;
background: #e87c1e url(../images/actions_bkg.png?v=v2.3.0) no-repeat scroll right top;
color: #fff;
padding-right: 8px;
cursor: pointer;
@@ -1180,10 +1180,10 @@ select#org_id {
cursor: not-allowed;
}
.dragHover {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0b);
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0);
}
.edit_mode .dashlet {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0b);
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.3.0);
padding: 5px;
margin: 0;
position: relative;
@@ -1215,7 +1215,7 @@ table.prop_table {
top: 0;
right: 0;
z-index: 10;
background: transparent url(../images/delete.png?v=v2.3.0b) no-repeat center;
background: transparent url(../images/delete.png?v=v2.3.0) no-repeat center;
}
td.prop_value {
text-align: left;
@@ -1409,17 +1409,17 @@ a.summary, a.summary:hover {
}
.message_info {
border: 1px solid #993;
background: url(../images/info-mini.png?v=v2.3.0b) 1em 1em no-repeat #ffc;
background: url(../images/info-mini.png?v=v2.3.0) 1em 1em no-repeat #ffc;
padding-left: 3em;
}
.message_ok {
border: 1px solid #393;
background: url(../images/ok.png?v=v2.3.0b) 1em 1em no-repeat #cfc;
background: url(../images/ok.png?v=v2.3.0) 1em 1em no-repeat #cfc;
padding-left: 3em;
}
.message_error {
border: 1px solid #933;
background: url(../images/error.png?v=v2.3.0b) 1em 1em no-repeat #fcc;
background: url(../images/error.png?v=v2.3.0) 1em 1em no-repeat #fcc;
padding-left: 3em;
}
.fg-menu a img {
@@ -1547,18 +1547,18 @@ div.explain-printable {
}
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter span {
padding-left: 20px;
background: url(../images/eye-open-555.png?v=v2.3.0b) 2px center no-repeat;
background: url(../images/eye-open-555.png?v=v2.3.0) 2px center no-repeat;
}
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter.strikethrough span {
text-decoration: line-through;
background: url(../images/eye-closed-555.png?v=v2.3.0b) 2px center no-repeat;
background: url(../images/eye-closed-555.png?v=v2.3.0) 2px center no-repeat;
}
.printable-version legend {
padding-left: 26px;
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.3.0b) 8px center no-repeat;
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.3.0) 8px center no-repeat;
}
.printable-version .strikethrough legend {
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.3.0b) 8px center no-repeat;
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.3.0) 8px center no-repeat;
}
.printable-version fieldset.strikethrough span {
display: none;
@@ -1577,7 +1577,7 @@ span.refresh-button {
width: 21px;
height: 18px;
cursor: pointer;
background: transparent url(../images/refresh-fff.png?v=v2.3.0b) left center no-repeat;
background: transparent url(../images/refresh-fff.png?v=v2.3.0) left center no-repeat;
}
.case-log-history-entry {
display: block;
@@ -1705,7 +1705,7 @@ span.refresh-button {
#itop-breadcrumb .breadcrumb-item a::after {
content: '';
position: absolute;
background-image: url(../images/breadcrumb-separator.png?v=v2.3.0b);
background-image: url(../images/breadcrumb-separator.png?v=v2.3.0);
background-repeat: no-repeat;
width: 8px;
height: 16px;
@@ -1746,3 +1746,20 @@ span.refresh-button {
.mfp-close {
cursor: pointer !important;
}
.qtip-content {
font-size: 12px;
}
.qtip-content p:first-child {
margin-top: 0px;
}
.qtip-content p:last-child {
margin-bottom: 0px;
}
.synchro-source-title {
font-weight: bolder;
}
.synchro-source-description {
font-size: smaller;
margin-top: 3px;
margin-bottom: 1px;
}

View File

@@ -1869,3 +1869,23 @@ span.refresh-button {
.mfp-close {
cursor: pointer !important;
}
.qtip-content {
font-size: 12px;
}
.qtip-content p:first-child {
margin-top: 0px;
}
.qtip-content p:last-child {
margin-bottom: 0px;
}
.synchro-source {
}
.synchro-source-title {
font-weight: bolder;
}
.synchro-source-description {
font-size: smaller;
margin-top: 3px;
margin-bottom: 1px;
}

View File

@@ -244,6 +244,7 @@ class BackupExec implements iScheduledProcess
{
break;
}
$iNextPos = false; // necessary on sundays
}
}

View File

@@ -552,7 +552,7 @@
<static>false</static>
<access>public</access>
<type>Overload-DBObject</type>
<code><![CDATA[ public function DBDeleteSingleObject(&$oDeletionPlan)
<code><![CDATA[ public function DBDeleteSingleObject()
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
@@ -1582,7 +1582,8 @@
$sStateAttCode = MetaModel::GetStateAttributeCode($sSubClass);
if ($sStateAttCode != '')
{
$oSearch = DBSearch::FromOQL("SELECT $sSubClass AS t JOIN $sLnkClass AS lnk ON lnk.$sExtKeyToRemote = t.id WHERE $sExtKeyToMe = :myself AND $sStateAttCode NOT IN ('rejected', 'resolved', 'closed')", array('myself' => $this->GetKey()));
// Todo: base the search condition on operational_status = 'ongoing' for a more flexible behavior
$oSearch = DBSearch::FromOQL("SELECT $sSubClass AS t JOIN $sLnkClass AS lnk ON lnk.$sExtKeyToRemote = t.id WHERE lnk.$sExtKeyToMe = :myself AND t.$sStateAttCode NOT IN ('rejected', 'resolved', 'closed')", array('myself' => $this->GetKey()));
$aSearches[$sSubClass] = $oSearch;
$oSet = new DBObjectSet($oSearch);

View File

@@ -105,7 +105,7 @@ try
{
$oP->add("<div class=\"header_message message_info\">Sorry, iTop is in <b>demonstration mode</b>: the configuration file cannot be edited.</div>");
}
if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled')
else if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled')
{
$oP->add("<div class=\"header_message message_info\">iTop interactive edition of the configuration as been disabled. See <tt>'config_editor' => 'disabled'</tt> in the configuration file.</div>");
}

View File

@@ -1229,7 +1229,7 @@
if (!MetaModel::IsValidClass('UserRequest')) return true; // Do nothing
$oLog = $this->Get('public_log');
$sLogPublic = $oLog->GetModifiedEntry();
$sLogPublic = $oLog->GetModifiedEntry('html');
if ($sLogPublic != '')
{
$sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket";
@@ -1247,7 +1247,7 @@
}
$oLog = $this->Get('private_log');
$sLogPrivate = $oLog->GetModifiedEntry();
$sLogPrivate = $oLog->GetModifiedEntry('html');
if ($sLogPrivate != '')
{
$sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket";
@@ -1274,7 +1274,7 @@
<code><![CDATA[ public function UpdateChildIncidentLog()
{
$oLog = $this->Get('public_log');
$sLogPublic = $oLog->GetModifiedEntry();
$sLogPublic = $oLog->GetModifiedEntry('html');
if ($sLogPublic != '')
{
$sOQL = "SELECT Incident WHERE parent_incident_id=:ticket";
@@ -1292,7 +1292,7 @@
}
$oLog = $this->Get('private_log');
$sLogPrivate = $oLog->GetModifiedEntry();
$sLogPrivate = $oLog->GetModifiedEntry('html');
if ($sLogPrivate != '')
{
$sOQL = "SELECT Incident WHERE parent_incident_id=:ticket";

View File

@@ -622,7 +622,7 @@
<parent_att/>
<name_att/>
<tooltip_att/>
<title>Catégories</title>
<title>Class:FAQCategory</title>
<actions/>
<levels>
<level id="1">
@@ -630,7 +630,7 @@
<parent_att>category_id</parent_att>
<name_att>title</name_att>
<tooltip_att>summary</tooltip_att>
<title>FAQs</title>
<title>Class:FAQ</title>
<fields>
<field id="error_code"/>
<field id="key_words">

View File

@@ -125,5 +125,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:lnkDocumentToError/Attribute:error_name+' => '',
'Class:FAQ/Attribute:category_name' => 'Kategoriename',
'Class:FAQ/Attribute:category_name+' => '',
'Brick:Portal:FAQ:Menu' => 'FAQ',
'Brick:Portal:FAQ:Title' => 'Oft gestellte Fragen (FAQs)',
'Brick:Portal:FAQ:Title+' => '<p>In Eile?</p><p>Sehen Sie sich die meistgestellten Fragen an (FAQs) und finden Sie (eventuell) die Antwort direkt dort.</p>',
));
?>

View File

@@ -187,5 +187,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Menu:FAQ' => 'Preguntas y Respuestas Frecuentes',
'Menu:FAQ+' => 'Preguntas y Respuestas Frecuentes',
'Brick:Portal:FAQ:Menu' => 'FAQ',
'Brick:Portal:FAQ:Title' => 'Preguntas y Respuestas Frecuentes',
'Brick:Portal:FAQ:Title+' => '<p>¿En una prisa?</p><p>Vea la lista de las preguntas más comunes y encontrará (tal vez) la respuesta inmediata a sus necesidades.</p>',
));
?>

View File

@@ -45,8 +45,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Error:HTTP:GetHelp' => 'Kontaktujte prosím administrátora, pokud problém přetrvá.',
'Error:XHR:Fail' => 'Data se nepodařilo načíst, kontaktujte prosím administrátora.',
'Portal:Datatables:Language:Processing' => 'Počkejte prosím',
'Portal:Datatables:Language:Search' => 'filtr :',
'Portal:Datatables:Language:LengthMenu' => 'Zobrazit _MENU_ položek na stránku',
'Portal:Datatables:Language:Search' => 'Filtr :',
'Portal:Datatables:Language:LengthMenu' => 'Zobrazit _MENU_ položek na stránku',
'Portal:Datatables:Language:ZeroRecords' => 'Žádný výsledek',
'Portal:Datatables:Language:Info' => 'Stránka _PAGE_ z _PAGES_',
'Portal:Datatables:Language:InfoEmpty' => 'Žádná informace',
@@ -61,6 +61,9 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Portal:Datatables:Language:Sort:Descending' => 'řadit sestupně',
'Portal:Autocomplete:NoResult' => 'Žádná data',
'Portal:Attachments:DropZone:Message' => 'Přesuňte soubory myší pro vložení',
'Portal:File:None' => 'No file',
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
));
// UserProfile brick

View File

@@ -0,0 +1,117 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2016 ITOMIG GmbH
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Portal
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Page:DefaultTitle' => 'iTop - Benutzer Portal',
'Page:PleaseWait' => 'Bitte warten...',
'Page:Home' => 'Start',
'Page:GoPortalHome' => 'Startseite',
'Page:GoPreviousPage' => 'vorherige Seite',
'Portal:Button:Submit' => 'Abschicken',
'Portal:Button:Cancel' => 'Zurück',
'Portal:Button:Close' => 'Schließen',
'Portal:Button:Add' => 'Hinzu',
'Portal:Button:Remove' => 'Wegnehmen',
'Portal:Button:Delete' => 'Löschen',
'Portal:EnvironmentBanner:Title' => 'Sie sind im Moment im <strong>%1$s</strong> Modus',
'Portal:EnvironmentBanner:GoToProduction' => 'Zurück zum PRODUCTION Modus',
'Error:HTTP:404' => 'Seite nicht gefunden.',
'Error:HTTP:500' => 'Oops! Es ist ein Fehler aufgetreten.',
'Error:HTTP:GetHelp' => 'Bitte kontaktieren Sie Ihren iTop administrator falls das Problem öfter auftaucht.',
'Error:XHR:Fail' => 'Konnte Daten nicht laden, bitte kontaktieren Sie Ihren iTop administrator',
'Portal:Datatables:Language:Processing' => 'Bitte warten...',
'Portal:Datatables:Language:Search' => 'Filter :',
'Portal:Datatables:Language:LengthMenu' => 'Anzahl _MENU_ Einträge pro Seite',
'Portal:Datatables:Language:ZeroRecords' => 'Keine Resultate',
'Portal:Datatables:Language:Info' => 'Seite _PAGE_ von _PAGES_',
'Portal:Datatables:Language:InfoEmpty' => 'Keine Information',
'Portal:Datatables:Language:InfoFiltered' => 'gefiltert aus _MAX_ Resultaten',
'Portal:Datatables:Language:EmptyTable' => 'Keine Daten in dieser Tabelle verfügbar',
'Portal:Datatables:Language:DisplayLength:All' => 'Alle',
'Portal:Datatables:Language:Paginate:First' => '1.Seite',
'Portal:Datatables:Language:Paginate:Previous' => 'vorherige',
'Portal:Datatables:Language:Paginate:Next' => 'Nächste',
'Portal:Datatables:Language:Paginate:Last' => 'Letzte',
'Portal:Datatables:Language:Sort:Ascending' => 'wähle aufsteigende Sortierung',
'Portal:Datatables:Language:Sort:Descending' => 'wähle abfallende Sortierung',
'Portal:Autocomplete:NoResult' => 'keine Daten',
'Portal:Attachments:DropZone:Message' => 'Legen Sie hier Ihre Files ab, um sie als Anhang dem Ticket hinzuzufügen',
'Portal:File:None' => 'Kein File vorhanden',
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Öffnen</a> / <a href="%4$s" class="file_download_link">Download</a>',
));
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:UserProfile:Name' => 'Benutzer Profil',
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'Mein Profil',
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Abmelden',
'Brick:Portal:UserProfile:Password:Title' => 'Passwort',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Passwort wählen',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Passwort bestätigen',
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'Um das Password zu ändern, kontaktieren Sie bitte Ihren iTop Administrator',
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'Kann das Passwort nicht ändern - bitte kontaktieren Sie Ihren iTop Administrator',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Persönliche Informationen',
'Brick:Portal:UserProfile:Photo:Title' => 'Foto',
));
// BrowseBrick brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Browse:Name' => 'List durchgehen',
'Brick:Portal:Browse:Mode:List' => 'Liste',
'Brick:Portal:Browse:Mode:Tree' => 'Baum',
'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
'Brick:Portal:Browse:Action:View' => 'Details',
'Brick:Portal:Browse:Action:Edit' => 'Editieren',
'Brick:Portal:Browse:Action:Create' => 'Erstellen',
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'Neue %1$s',
'Brick:Portal:Browse:Tree:ExpandAll' => 'Alle expandieren',
'Brick:Portal:Browse:Tree:CollapseAll' => 'Alle kollabieren',
'Brick:Portal:Browse:Filter:NoData' => 'Kein Eintrag',
));
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Manage:Name' => 'Einträge managen',
'Brick:Portal:Manage:Table:NoData' => 'Kein Eintrag.',
));
// ObjectBrick brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Object:Name' => 'Object',
'Brick:Portal:Object:Form:Create:Title' => 'Neue %1$s',
'Brick:Portal:Object:Form:Edit:Title' => 'Wird aktualisiert %2$s (%1$s)',
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
'Brick:Portal:Object:Form:Stimulus:Title' => 'Bitte die folgendenen Informationen ausfüllen:',
'Brick:Portal:Object:Form:Message:Saved' => 'Saved',
'Brick:Portal:Object:Search:Regular:Title' => 'Select %1$s (%2$s)',
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Select %1$s (%2$s)',
));
// CreateBrick brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Create:Name' => 'Schnelles Erstellen',
));
?>

View File

@@ -41,7 +41,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Error:HTTP:GetHelp' => 'Please contact your iTop administrator if the problem keeps happening.',
'Error:XHR:Fail' => 'Could not load data, please contact your iTop administrator',
'Portal:Datatables:Language:Processing' => 'Please wait...',
'Portal:Datatables:Language:Search' => 'filter :',
'Portal:Datatables:Language:Search' => 'Filter:',
'Portal:Datatables:Language:LengthMenu' => 'Display _MENU_ items per page',
'Portal:Datatables:Language:ZeroRecords' => 'No result',
'Portal:Datatables:Language:Info' => 'Page _PAGE_ of _PAGES_',
@@ -57,6 +57,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Portal:Datatables:Language:Sort:Descending' => 'enable for a descending sort',
'Portal:Autocomplete:NoResult' => 'No data',
'Portal:Attachments:DropZone:Message' => 'Drop your files to add them as attachments',
'Portal:File:None' => 'No file',
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
));
// UserProfile brick

View File

@@ -0,0 +1,116 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Portal
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Page:DefaultTitle' => 'iTop User portal',
'Page:PleaseWait' => 'Please wait...',
'Page:Home' => 'Bienvenido',
'Page:GoPortalHome' => 'Regresar a bienvenida',
'Page:GoPreviousPage' => 'página anterior',
'Portal:Button:Submit' => 'Enviar',
'Portal:Button:Cancel' => 'Cancelar',
'Portal:Button:Close' => 'Cerrar',
'Portal:Button:Add' => 'Añadir',
'Portal:Button:Remove' => 'Eliminar',
'Portal:Button:Delete' => 'Borrar',
'Error:HTTP:404' => 'Página no encontrada',
'Error:HTTP:500' => '¡Vaya! Ha ocurrido un error.',
'Error:HTTP:GetHelp' => 'Póngase en contacto con el administrador de iTop si el problema persiste.',
'Error:XHR:Fail' => 'No se pudieron cargar datos, póngase en contacto con su administrador de iTop',
'Portal:Datatables:Language:Processing' => 'Por favor esperar...',
'Portal:Datatables:Language:Search' => 'Filtrar:',
'Portal:Datatables:Language:LengthMenu' => 'Mostrar _MENU_ elementos por página',
'Portal:Datatables:Language:ZeroRecords' => 'Sin resultados',
'Portal:Datatables:Language:Info' => 'Página _PAGE_ de _PAGES_',
'Portal:Datatables:Language:InfoEmpty' => 'Sin información',
'Portal:Datatables:Language:InfoFiltered' => 'Filtrada de _MAX_ elementos',
'Portal:Datatables:Language:EmptyTable' => 'No hay datos disponibles en esta tabla',
'Portal:Datatables:Language:DisplayLength:All' => 'Todas',
'Portal:Datatables:Language:Paginate:First' => 'primero',
'Portal:Datatables:Language:Paginate:Previous' => 'Anterior',
'Portal:Datatables:Language:Paginate:Next' => 'Siguiente',
'Portal:Datatables:Language:Paginate:Last' => 'Último',
'Portal:Datatables:Language:Sort:Ascending' => 'Habilitar para un orden ascendente',
'Portal:Datatables:Language:Sort:Descending' => 'Habilitar para un tipo descendente',
'Portal:Autocomplete:NoResult' => 'Sin datos',
'Portal:Attachments:DropZone:Message' => 'Agrega tus archivos para agregarlos como documentos adjuntos',
'Portal:File:None' => 'No file',
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
));
// UserProfile brick
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:UserProfile:Name' => 'Perfil del usuario',
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'Mi perfil',
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Desconectarse',
'Brick:Portal:UserProfile:Password:Title' => 'Contraseña',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Elegir una contraseña',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirmar contraseña',
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'Para cambiar su contraseña, póngase en contacto con su administrador de iTop',
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'No se puede cambiar la contraseña, póngase en contacto con el administrador de iTop',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Informaciones personales',
'Brick:Portal:UserProfile:Photo:Title' => 'Foto',
));
// BrowseBrick brick
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Browse:Name' => 'Buscar en todos los elementos',
'Brick:Portal:Browse:Mode:List' => 'Lista',
'Brick:Portal:Browse:Mode:Tree' => 'Árbol',
'Brick:Portal:Browse:Action:Drilldown' => 'Desglose',
'Brick:Portal:Browse:Action:View' => 'Detalles',
'Brick:Portal:Browse:Action:Edit' => 'Editar',
'Brick:Portal:Browse:Action:Create' => 'Crear',
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'Nuevo %1$s',
'Brick:Portal:Browse:Tree:ExpandAll' => 'Expandir todo',
'Brick:Portal:Browse:Tree:CollapseAll' => 'Desplegar todo',
'Brick:Portal:Browse:Filter:NoData' => 'Sin objeto',
));
// ManageBrick brick
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Manage:Name' => 'Administrar elementos',
'Brick:Portal:Manage:Table:NoData' => 'Sin objeto.',
));
// ObjectBrick brick
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Object:Name' => 'Object',
'Brick:Portal:Object:Form:Create:Title' => 'New %1$s',
'Brick:Portal:Object:Form:Edit:Title' => 'Updating %2$s (%1$s)',
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
'Brick:Portal:Object:Form:Stimulus:Title' => 'Please, fill the following informations:',
'Brick:Portal:Object:Form:Message:Saved' => 'Saved',
'Brick:Portal:Object:Search:Regular:Title' => 'Select %1$s (%2$s)',
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Select %1$s (%2$s)',
));
// CreateBrick brick
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Create:Name' => 'Creación rápida',
));
?>

View File

@@ -57,6 +57,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Portal:Datatables:Language:Sort:Descending' => 'activer pour trier la colonne par ordre décroissant',
'Portal:Autocomplete:NoResult' => 'Aucun résultat',
'Portal:Attachments:DropZone:Message' => 'Déposez vos fichiers pour les ajouter en pièces jointes',
'Portal:File:None' => 'Aucun fichier',
'Portal:File:DisplayInfo' => '<a href="%2$s" class="file_download_link">%1$s</a>',
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Ouvrir</a> / <a href="%4$s" class="file_download_link">Télécharger</a>',
));
// UserProfile brick

View File

@@ -2,7 +2,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal-base/1.0.0', array(
'itop-portal-base/1.0.1', array(
// Identification
'label' => 'Portal Development Library',
'category' => 'Portal',

View File

@@ -41,6 +41,7 @@ use \Combodo\iTop\Portal\Brick\BrowseBrick;
class BrowseBrickController extends BrickController
{
const LEVEL_SEPARATOR = '-';
public static $aOptionalAttributes = array('tooltip_att');
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
{
@@ -93,11 +94,19 @@ class BrowseBrickController extends BrickController
{
// Retrieving class alias for all depth
array_unshift($aLevelsClasses, $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetClassAlias());
// Joining queries from bottom-up
if ($i < $iLoopMax)
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att']);
$aRealiasingMap = array();
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att'], TREE_OPERATOR_EQUALS, $aRealiasingMap);
foreach ($aLevelsPropertiesKeys as $sLevelAlias)
{
if (array_key_exists($sLevelAlias, $aRealiasingMap))
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($aRealiasingMap[$sLevelAlias], $sLevelAlias);
}
}
}
// Adding search clause
@@ -168,7 +177,7 @@ class BrowseBrickController extends BrickController
}
}
$oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search'];
// Testing appropriate data loading mode if we are in auto
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
{
@@ -259,7 +268,41 @@ class BrowseBrickController extends BrickController
{
$oSet = new DBObjectSet($oQuery);
}
// Optimizing the ObjectSet to retrieve only necessary columns
$aColumnAttrs = array();
foreach ($oSet->GetFilter()->GetSelectedClasses() as $sTmpClassAlias => $sTmpClassName)
{
if (isset($aLevelsProperties[$sTmpClassAlias]))
{
$aTmpLevelProperties = $aLevelsProperties[$sTmpClassAlias];
// Mandatory main attribute
$aTmpColumnAttrs = array($aTmpLevelProperties['name_att']);
// Optionnal attributes, only if in list mode
if ($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_LIST)
{
foreach ($aTmpLevelProperties['fields'] as $aTmpField)
{
$aTmpColumnAttrs[] = $aTmpField['code'];
}
}
// Optional attributes
foreach(static::$aOptionalAttributes as $sOptionalAttribute)
{
if($aTmpLevelProperties[$sOptionalAttribute] !== null)
{
$aTmpColumnAttrs[] = $aTmpLevelProperties[$sOptionalAttribute];
}
}
$aColumnAttrs[$sTmpClassAlias] = $aTmpColumnAttrs;
}
}
$oSet->OptimizeColumnLoad($aColumnAttrs);
// Sorting objects through defined order (in DM)
$oSet->SetOrderByClasses();
// Retrieving results and organizing them for templating
$aItems = array();
while ($aCurrentRow = $oSet->FetchAssoc())
@@ -338,10 +381,16 @@ class BrowseBrickController extends BrickController
{
$sCurrentLevelAlias = $sLevelAliasPrefix . static::LEVEL_SEPARATOR . $aLevel['id'];
$oSearch = DBSearch::CloneWithAlias(DBSearch::FromOQL($aLevel['oql']), $sCurrentLevelAlias);
// Restricting to the allowed scope
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oSearch->GetClass(), UR_ACTION_READ);
$oSearch = ($oScopeSearch !== null) ? $oSearch->Intersect($oScopeSearch) : null;
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
if ($oSearch !== null)
{
$aLevelsProperties[$sCurrentLevelAlias] = array(
@@ -561,7 +610,15 @@ class BrowseBrickController extends BrickController
$aRow[$key]['fields'] = array();
foreach ($aLevelsProperties[$key]['fields'] as $aField)
{
$aRow[$key]['fields'][$aField['code']] = $value->Get($aField['code']);
$oAttDef = MetaModel::GetAttributeDef(get_class($value), $aField['code']);
if ($oAttDef->GetEditClass() === 'Duration')
{
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetAsHTML($value->Get($aField['code']));
}
else
{
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetValueLabel($value->Get($aField['code']));
}
}
}
}
@@ -630,5 +687,3 @@ class BrowseBrickController extends BrickController
}
}
?>

View File

@@ -37,8 +37,17 @@ class DefaultController
// Doing it only for tile visible on home page to avoid unnecessary rendering
if (($oBrick->GetVisibleHome() === true) && ($oBrick->GetTileControllerAction() !== null))
{
$oController = new \Combodo\iTop\Portal\Controller\ManageBrickController($oRequest, $oApp);
$aData['aTilesRendering'][$oBrick->GetId()] = $oController->HomeAction($oRequest, $oApp);
$aControllerActionParts = explode('::', $oBrick->GetTileControllerAction());
if (count($aControllerActionParts) !== 2)
{
$oApp->abort(500, 'Tile controller action must be of form "\Namespace\ControllerClass::FunctionName" for brick "' . $oBrick->GetId() . '"');
}
$sControllerName = $aControllerActionParts[0];
$sControllerAction = $aControllerActionParts[1];
$oController = new $sControllerName($oRequest, $oApp, $oBrick->GetId());
$aData['aTilesRendering'][$oBrick->GetId()] = $oController->$sControllerAction($oRequest, $oApp, $oBrick->GetId());
}
}

View File

@@ -28,6 +28,8 @@ use \MetaModel;
use \AttributeDefinition;
use \AttributeDate;
use \AttributeDateTime;
use \AttributeDuration;
use \AttributeSubItem;
use \DBSearch;
use \DBObjectSearch;
use \DBObjectSet;
@@ -59,6 +61,15 @@ class ManageBrickController extends BrickController
// Getting search value
$sSearchValue = $oRequest->get('sSearchValue', null);
// Getting area columns properties
$aColumnsAttrs = $oBrick->GetFields();
// Adding friendlyname attribute to the list is not already in it
$sTitleAttrCode = 'friendlyname';
if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aColumnsAttrs))
{
$aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
}
// Starting to build query
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
@@ -198,6 +209,11 @@ class ManageBrickController extends BrickController
if ($oDistinctScopeQuery != null)
{
$oDistinctQuery = $oDistinctQuery->Intersect($oDistinctScopeQuery);
// - Allowing all data if necessary
if ($oDistinctScopeQuery->IsAllDataAllowed())
{
$oDistinctQuery->AllowAllData();
}
}
// Adding grouping conditions
$oFieldExp = new FieldExpression($sGroupingAreaAttCode, $sParentAlias);
@@ -252,7 +268,19 @@ class ManageBrickController extends BrickController
// Note : Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass
// Note : We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights
$oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $aGroupingAreasValue['value'], UR_ACTION_READ);
$oAreaQuery = ($oScopeQuery !== null) ? $oAreaQuery->Intersect($oScopeQuery) : null;
if ($oScopeQuery !== null)
{
$oAreaQuery = $oAreaQuery->Intersect($oScopeQuery);
// - Allowing all data if necessary
if ($oScopeQuery->IsAllDataAllowed())
{
$oAreaQuery->AllowAllData();
}
}
else
{
$oAreaQuery = null;
}
$aQueries[$sKey] = $oAreaQuery;
}
@@ -264,6 +292,7 @@ class ManageBrickController extends BrickController
// - Check how many records there is.
// - Update $sDataLoading with its new value regarding the number of record and the threshold
$oCountSet = new DBObjectSet($oQuery);
$oCountSet->OptimizeColumnLoad(array());
$fThreshold = (float) MetaModel::GetModuleSetting($oApp['combodo.portal.instance.id'], 'lazy_loading_threshold');
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
unset($oCountSet);
@@ -285,6 +314,7 @@ class ManageBrickController extends BrickController
// Getting total records number
$oCountSet = new DBObjectSet($oQuery);
$oCountSet->OptimizeColumnLoad(array($oQuery->GetClassAlias() => $aColumnsAttrs));
$aData['recordsTotal'] = $oCountSet->Count();
$aData['recordsFiltered'] = $oCountSet->Count();
unset($oCountSet);
@@ -296,6 +326,8 @@ class ManageBrickController extends BrickController
{
$oSet = new DBObjectSet($oQuery);
}
$oSet->OptimizeColumnLoad(array($oQuery->GetClassAlias() => $aColumnsAttrs));
$oSet->SetOrderByClasses();
$aSets[$sKey] = $oSet;
}
}
@@ -306,15 +338,7 @@ class ManageBrickController extends BrickController
{
// Set properties
$sCurrentClass = $sKey;
$sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sCurrentClass);
// Getting area columns properties
$aColumnsAttrs = $oBrick->GetFields();
// Adding friendlyname attribute to the list is not already in it
if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aColumnsAttrs))
{
$aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
}
// Defining which attribute will open the edition form)
$sMainActionAttrCode = $aColumnsAttrs[0];
@@ -367,7 +391,7 @@ class ManageBrickController extends BrickController
);
}
}
$oAttDef = MetaModel::GetAttributeDef($sCurrentClass, $sItemAttr);
if ($oAttDef->IsExternalKey())
{
@@ -387,6 +411,10 @@ class ManageBrickController extends BrickController
}
}
}
elseif ($oAttDef instanceof AttributeSubItem || $oAttDef instanceof AttributeDuration)
{
$sValue = $oAttDef->GetAsHTML($oCurrentRow->Get($sItemAttr));
}
else
{
$sValue = $oAttDef->GetValueLabel($oCurrentRow->Get($sItemAttr));
@@ -446,5 +474,3 @@ class ManageBrickController extends BrickController
}
}
?>

View File

@@ -32,6 +32,7 @@ use \IssueLog;
use \MetaModel;
use \DBSearch;
use \DBObjectSearch;
use \FalseExpression;
use \BinaryExpression;
use \FieldExpression;
use \VariableExpression;
@@ -39,6 +40,8 @@ use \ListExpression;
use \ScalarExpression;
use \DBObjectSet;
use \cmdbAbstractObject;
use \AttributeEnum;
use \AttributeFinalClass;
use \UserRights;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\SecurityHelper;
@@ -83,7 +86,7 @@ class ObjectController extends AbstractController
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
@@ -155,7 +158,7 @@ class ObjectController extends AbstractController
}
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
@@ -275,23 +278,26 @@ class ObjectController extends AbstractController
}
// Retrieving origin object
$oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
// Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
if (!strpos($sMethodName, '::'))
{
$sTargetObject = $sMethodName($oOriginObject);
$oTargetObject = $sMethodName($oOriginObject);
}
else
{
$aMethodNameParts = explode('::', $sMethodName);
$sTargetObject = $aMethodNameParts[0]::$aMethodNameParts[1]($oOriginObject);
$sMethodClass = $aMethodNameParts[0];
$sMethodName = $aMethodNameParts[1];
$oTargetObject = $sMethodClass::$sMethodName($oOriginObject);
}
// Preparing redirection
// - Route
$aRouteParams = array(
'sObjectClass' => get_class($sTargetObject)
'sObjectClass' => get_class($oTargetObject)
);
$sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
// - Request
@@ -327,7 +333,7 @@ class ObjectController extends AbstractController
// }
// Retrieving object
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
// We should never be there as the secuirty helper makes sure that the object exists, but just in case.
@@ -335,6 +341,9 @@ class ObjectController extends AbstractController
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
// Retrieving request parameters
$sOperation = $oRequest->request->get('operation');
// Preparing a dedicated form for the stimulus application
$aFormProperties = array(
'id' => 'apply-stimulus',
@@ -372,14 +381,39 @@ class ObjectController extends AbstractController
'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
);
// TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
// Instead, we apply the stimulus directly here and then go to the edited object.
if ($sOperation === null)
{
if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
{
$sOperation = 'redirect';
$oSubRequest = $oRequest;
$oSubRequest->request->set('operation', 'submit');
$oSubRequest->request->set('stimulus_code', null);
$aData = array('sMode' => 'apply_stimulus');
$aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
// Redefining the array to be as simple as possible :
$aData = array('redirection' =>
array('url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)))
);
}
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if ($oRequest->request->get('operation') === null)
if ($sOperation === null)
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
elseif ($sOperation === 'redirect')
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/modal/mode_loader.html.twig', $aData);
}
else
{
$oResponse = $oApp->json($aData);
@@ -428,7 +462,7 @@ class ObjectController extends AbstractController
}
else
{
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId);
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
}
// Preparing transitions only if we are currently going through one
@@ -473,9 +507,16 @@ class ObjectController extends AbstractController
->SetMode($sMode)
->SetActionRulesToken($sActionRulesToken)
->SetRenderer($oFormRenderer)
->SetFormProperties($aFormProperties)
->Build();
->SetFormProperties($aFormProperties);
if ($sMode === 'apply_stimulus')
{
$aEditFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, ObjectFormManager::ENUM_MODE_APPLY_STIMULUS);
$oFormManager->MergeFormProperties($aEditFormProperties);
}
$oFormManager->Build();
// Check the number of editable fields
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
}
@@ -613,6 +654,8 @@ class ObjectController extends AbstractController
// Retrieving parameters
$sQuery = $aRequestContent['sQuery'];
$sFormPath = $aRequestContent['sFormPath'];
$sFieldId = $aRequestContent['sFieldId'];
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
@@ -624,7 +667,8 @@ class ObjectController extends AbstractController
// Retrieving host object for future DBSearch parameters
if ($sHostObjectId !== null)
{
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
}
else
{
@@ -665,20 +709,58 @@ class ObjectController extends AbstractController
// Building search query
// - Retrieving target object class from attcode
$oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
}
elseif ($oTargetAttDef->IsLinkSet())
{
throw new Exception('Search autocomplete cannot apply on AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' (' . $sHostObjectClass . '->' . $sTargetAttCode . ') given.');
}
else
{
$sTargetObjectClass = $oTargetAttDef->GetTargetClass();
}
// - Base query from meta model
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oSearch = $oTemplateFieldSearch;
}
else
{
$oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
}
// - Adding query condition
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
// - Intersecting with scope constraints
$oSearch = $oSearch->Intersect($oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ));
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
if ($oTargetAttDef->GetEditClass() !== 'CustomFields')
{
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
$oSearch = $oSearch->Intersect($oScopeSearch);
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
}
// Retrieving results
// - Preparing object set
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
// Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
$oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
if ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oSet->SetLimit(static::DEFAULT_COUNT_PER_PAGE_LIST);
}
else
{
$oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
}
// - Retrieving objects
while ($oItem = $oSet->Fetch())
{
@@ -729,7 +811,8 @@ class ObjectController extends AbstractController
// Retrieving host object for future DBSearch parameters
if ($sHostObjectId !== null)
{
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
}
else
{
@@ -795,6 +878,12 @@ class ObjectController extends AbstractController
$sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
}
}
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
$oRequestTemplate = $oHostObject->Get($sTargetAttCode);
$oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
$sTargetObjectClass = $oTemplateFieldSearch->GetClass();
}
else
{
throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
@@ -803,16 +892,18 @@ class ObjectController extends AbstractController
// - Retrieving class attribute list
$aAttCodes = ApplicationHelper::GetLoadedListFromClass($oApp, $sTargetObjectClass, 'list');
// - Adding friendlyname attribute to the list is not already in it
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
$sTitleAttCode = 'friendlyname';
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodes))
{
$aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
}
// - Retrieving scope search
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
$aInternalParams = array();
if ($oScopeSearch === null)
if (($oScopeSearch === null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
@@ -827,6 +918,11 @@ class ObjectController extends AbstractController
{
$oSearch = $oScopeSearch;
}
elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
{
// Note : $oTemplateFieldSearch has been defined in the "Retrieving target object class from attcode" part, it is not available otherwise
$oSearch = $oTemplateFieldSearch;
}
// - Filtering objects to ignore
if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
@@ -839,7 +935,7 @@ class ObjectController extends AbstractController
}
$oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
}
// - Adding query condition
$aInternalParams['this'] = $oHostObject;
if ($sQuery !== null)
@@ -851,7 +947,35 @@ class ObjectController extends AbstractController
$oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
$sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
// Building expression for the current attcode
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
// - For attributes that need conversion from their display value to storage value
// Note : This is dirty hack that will need to be refactored in the OQL core in order to be nicer and to be extended to other types such as dates etc...
if (($oAttDef instanceof AttributeEnum) || ($oAttDef instanceof AttributeFinalClass))
{
// Looking up storage value
$aMatchedCodes = array();
foreach ($oAttDef->GetAllowedValues() as $sValueCode => $sValueLabel)
{
if (stripos($sValueLabel, $sQuery) !== false)
{
$aMatchedCodes[] = $sValueCode;
}
}
// Building expression
if (!empty($aMatchedCodes))
{
$oEnumeratedListExpr = ListExpression::FromScalars($aMatchedCodes);
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'IN', $oEnumeratedListExpr);
}
else
{
$oBinExpr = new FalseExpression();
}
}
// - For regular attributs
else
{
$oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
}
// Adding expression to the full expression (all attcodes)
if ($i === 0)
{
@@ -868,7 +992,17 @@ class ObjectController extends AbstractController
}
// - Intersecting with scope constraints
$oSearch = $oSearch->Intersect($oScopeSearch);
// Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
// It is the responsability of the template designer to write the right query so the user see only what he should.
if (($oScopeSearch !== null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
{
$oSearch = $oSearch->Intersect($oScopeSearch);
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
}
// Retrieving results
// - Preparing object set
@@ -909,7 +1043,7 @@ class ObjectController extends AbstractController
// Checking if we can view the object
if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oItem->Get($sAttCode))))
{
$aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->GetKey()));
$aAttProperties['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oItem->Get($sAttCode)));
}
}
else
@@ -946,7 +1080,7 @@ class ObjectController extends AbstractController
'sFormManagerData' => $sFormManagerData
)
);
if ($oRequest->isXmlHttpRequest())
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
@@ -1001,7 +1135,8 @@ class ObjectController extends AbstractController
// Retrieving host object for future DBSearch parameters
if ($sHostObjectId !== null)
{
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
}
else
{
@@ -1041,7 +1176,7 @@ class ObjectController extends AbstractController
// // - Retrieving class attribute list
// $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetObjectClass, 'list'));
// // - Adding friendlyname attribute to the list is not already in it
// $sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sTargetObjectClass);
// $sTitleAttrCode = 'friendlyname';
// if (($sTitleAttrCode !== null) && !in_array($sTitleAttrCode, $aAttCodes))
// {
// $aAttCodes = array_merge(array($sTitleAttrCode), $aAttCodes);
@@ -1092,6 +1227,11 @@ class ObjectController extends AbstractController
// }
// - Intersecting with scope constraints
$oSearch = $oSearch->Intersect($oScopeSearch);
// - Allowing all data if necessary
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
// Retrieving results
// - Preparing object set
@@ -1246,7 +1386,8 @@ class ObjectController extends AbstractController
}
}
$oResponse = $oApp->json($aData);
// Note : The Content-Type header is set to 'text/plain' in order to be IE9 compatible. Otherwise ('application/json') IE9 will download the response as a JSON file to the user computer...
$oResponse = $oApp->json($aData, 200, array('Content-Type' => 'text/plain'));
break;
case 'download':
@@ -1307,7 +1448,12 @@ class ObjectController extends AbstractController
}
// Building the search
$bIgnoreSilos = $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
$oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
if ($bIgnoreSilos === true)
{
$oSearch->AllowAllData();
}
$oSet = new DBObjectSet($oSearch);
$oSet->OptimizeColumnLoad($aObjectAttCodes);

View File

@@ -493,6 +493,9 @@ abstract class PortalBrick extends AbstractBrick
$this->SetDecorationClassNavigationMenu($optionalNodeValue);
}
break;
case 'tile_controller_action':
$this->SetTileControllerAction($oBrickSubNode->GetText(static::DEFAULT_TILE_CONTROLLER_ACTION));
break;
}
}

View File

@@ -29,6 +29,7 @@ use \MetaModel;
use \CMDBSource;
use \DBObject;
use \DBObjectSet;
use \DBSearch;
use \DBObjectSearch;
use \DBObjectSetComparator;
use \InlineImage;
@@ -49,6 +50,7 @@ class ObjectFormManager extends FormManager
const ENUM_MODE_VIEW = 'view';
const ENUM_MODE_EDIT = 'edit';
const ENUM_MODE_CREATE = 'create';
const ENUM_MODE_APPLY_STIMULUS = 'apply_stimulus';
protected $oApp;
protected $oObject;
@@ -92,7 +94,8 @@ class ObjectFormManager extends FormManager
}
else
{
$oObject = MetaModel::GetObject($sObjectClass, $aJson['formobject_id'], true);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oObject = MetaModel::GetObject($sObjectClass, $aJson['formobject_id'], true, true);
}
$oFormManager->SetObject($oObject);
@@ -482,6 +485,18 @@ class ObjectFormManager extends FormManager
{
$oField->SetReadOnly(true);
}
// - Else if it's must change, we force it as not readonly and not hidden
elseif (($iFieldFlags & OPT_ATT_MUSTCHANGE) === OPT_ATT_MUSTCHANGE)
{
$oField->SetReadOnly(false);
$oField->SetHidden(false);
}
// - Else if it's must prompt, we force it as not readonly and not hidden
elseif (($iFieldFlags & OPT_ATT_MUSTPROMPT) === OPT_ATT_MUSTPROMPT)
{
$oField->SetReadOnly(false);
$oField->SetHidden(false);
}
else
{
// Normal field
@@ -516,6 +531,61 @@ class ObjectFormManager extends FormManager
$oField->SetInformationEndpoint($this->oApp['url_generator']->generate('p_object_get_informations_json'));
}
}
// - Field that require to apply scope on its DM OQL
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\SelectObjectField')))
{
if ($this->oApp !== null)
{
$oScopeOriginal = ($oField->GetSearch() !== null) ? $oField->GetSearch() : DBSearch::FromOQL($oAttDef->GetValuesDef()->GetFilterExpression());
$oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oScopeOriginal->GetClass(), UR_ACTION_READ);
if ($oScopeSearch === null)
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $oScopeOriginal->GetClass() . ' class.');
$this->oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$oScopeOriginal = $oScopeOriginal->Intersect($oScopeSearch);
// Note : This is to skip the silo restriction on the final query
if ($oScopeSearch->IsAllDataAllowed())
{
$oScopeOriginal->AllowAllData();
}
$oScopeOriginal->SetInternalParams(array('this' => $this->oObject));
$oField->SetSearch($oScopeOriginal);
}
}
// - Field that require processing on their subfields
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\SubFormField')))
{
$oSubForm = $oField->GetForm();
if ($oAttDef->GetEditClass() === 'CustomFields')
{
// Retrieving only user data fields (not the metadata fields of the template)
if ($oSubForm->HasField('user_data'))
{
$oUserDataField = $oSubForm->GetField('user_data');
$oUserDataForm = $oUserDataField->GetForm();
foreach ($oUserDataForm->GetFields() as $oCustomField)
{
// - Field that require a search endpoint (OQL based dropdown list fields)
if (in_array(get_class($oCustomField), array('Combodo\\iTop\\Form\\Field\\SelectObjectField')))
{
if ($this->oApp !== null)
{
$sSearchEndpoint = $this->oApp['url_generator']->generate('p_object_search_generic', array(
'sTargetAttCode' => $oAttDef->GetCode(),
'sHostObjectClass' => get_class($this->oObject),
'sHostObjectId' => ($this->oObject->IsNew()) ? null : $this->oObject->GetKey(),
'ar_token' => $this->GetActionRulesToken(),
));
$oCustomField->SetSearchEndpoint($sSearchEndpoint);
}
}
}
}
}
}
}
else
{
@@ -539,7 +609,7 @@ class ObjectFormManager extends FormManager
// Note : This snippet is inspired from AttributeLinkedSet::MakeFormField()
$aAttCodesToDisplay = ApplicationHelper::GetLoadedListFromClass($this->oApp, $oField->GetTargetClass(), 'list');
// - Adding friendlyname attribute to the list is not already in it
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($oField->GetTargetClass());
$sTitleAttCode = 'friendlyname';
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay))
{
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
@@ -625,6 +695,87 @@ class ObjectFormManager extends FormManager
$this->oRenderer->SetForm($this->oForm);
}
/**
* Merging $this->aFormProperties with $aFormPropertiesToMerge. Merge only layout for now
*
* @param array $aFormPropertiesToMerge
* @throws Exception
*/
public function MergeFormProperties($aFormPropertiesToMerge)
{
if ($aFormPropertiesToMerge['layout'] !== null)
{
// Checking if we need to render the template from twig to html in order to parse the fields
if ($aFormPropertiesToMerge['layout']['type'] === 'twig')
{
// Creating sandbox twig env. to load and test the custom form template
$oTwig = new \Twig_Environment(new \Twig_Loader_String());
$sRendered = $oTwig->render($aFormPropertiesToMerge['layout']['content'], array('oRenderer' => $this->oRenderer, 'oObject' => $this->oObject));
}
else
{
$sRendered = $aFormPropertiesToMerge['layout']['content'];
}
// Parsing rendered template to find the fields
$oHtmlDocument = new \DOMDocument();
$oHtmlDocument->loadHTML('<root>' . $sRendered . '</root>');
// Adding fields to the list
$oXPath = new \DOMXPath($oHtmlDocument);
foreach ($oXPath->query('//div[@class="form_field"][@data-field-id]') as $oFieldNode)
{
$sFieldId = $oFieldNode->getAttribute('data-field-id');
$sFieldFlags = $oFieldNode->getAttribute('data-field-flags');
// $iFieldFlags = OPT_ATT_NORMAL;
// // Checking if field has form_path, if not, we add it
// if (!$oFieldNode->hasAttribute('data-form-path'))
// {
// $oFieldNode->setAttribute('data-form-path', $oForm->GetId());
// }
// Merging only fields that are already in the form
if (array_key_exists($sFieldId, $this->aFormProperties['fields']))
{
// Settings field flags from the data-field-flags attribute
foreach (explode(' ', $sFieldFlags) as $sFieldFlag)
{
if ($sFieldFlag !== '')
{
$sConst = 'OPT_ATT_' . strtoupper(str_replace('_', '', $sFieldFlag));
if (defined($sConst))
{
switch ($sConst)
{
case 'OPT_ATT_SLAVE':
case 'OPT_ATT_HIDDEN':
if (!array_key_exists($sFieldId, $this->aFormProperties['fields']))
{
$this->aFormProperties['fields'][$sFieldId] = array();
}
$this->aFormProperties['fields'][$sFieldId]['hidden'] = true;
break;
case 'OPT_ATT_READONLY':
if (!array_key_exists($sFieldId, $this->aFormProperties['fields']))
{
$this->aFormProperties['fields'][$sFieldId] = array();
}
$this->aFormProperties['fields'][$sFieldId]['read_only'] = true;
break;
}
}
else
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Flag "' . $sFieldFlag . '" is not valid for field [@data-field-id="' . $sFieldId . '"] in form[@id="' . $aFormPropertiesToMerge['id'] . '"]');
throw new Exception('Flag "' . $sFieldFlag . '" is not valid for field [@data-field-id="' . $sFieldId . '"] in form[@id="' . $aFormPropertiesToMerge['id'] . '"]');
}
}
}
}
}
}
}
/**
* Calls all form fields OnCancel method in order to delegate them the cleanup;
*
@@ -727,6 +878,9 @@ class ObjectFormManager extends FormManager
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
// Resetting caselog fields value, otherwise the value will stay in it after submit.
$this->oForm->ResetCaseLogFields();
if ($bWasModified)
{
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
@@ -791,7 +945,8 @@ class ObjectFormManager extends FormManager
// LinkedSet
if (!$oAttDef->IsIndirect())
{
$oLinkedObject = MetaModel::GetObject($sTargetClass, abs($iTargetId));
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oLinkedObject = MetaModel::GetObject($sTargetClass, abs($iTargetId), true, true);
$oValueSet->AddObject($oLinkedObject);
}
// LinkedSetIndirect
@@ -807,7 +962,8 @@ class ObjectFormManager extends FormManager
// Existing relation
else
{
$oLink = MetaModel::GetObject($sTargetClass, $iTargetId);
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oLink = MetaModel::GetObject($sTargetClass, $iTargetId, true, true);
}
$oValueSet->AddObject($oLink);
}
@@ -860,7 +1016,7 @@ class ObjectFormManager extends FormManager
else
{
$this->oObject->Set($sAttCode, $value);
}
}
}
}
$this->oObject->DoComputeValues();

View File

@@ -202,6 +202,7 @@ class ApplicationHelper
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
})
);
// A filter to format a string via the Dict::Format function
// Usage in twig : {{ 'String:ToTranslate'|dict_format() }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('dict_format', function($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null)
@@ -209,10 +210,12 @@ class ApplicationHelper
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
})
);
// Filters to enable base64 encode/decode
// Usage in twig : {{ 'String to encode'|base64_encode }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
// Filters to enable json decode (encode already exists)
// Usage in twig : {{ aSomeArray|json_decode }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('json_decode', function($sJsonString, $bAssoc = false)
@@ -220,6 +223,21 @@ class ApplicationHelper
return json_decode($sJsonString, $bAssoc);
})
);
// Filter to add itopversion to an url
$oApp['twig']->addFilter(new Twig_SimpleFilter('add_itop_version', function($sUrl)
{
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl . "?itopversion=" . ITOP_VERSION;
}
else
{
$sUrl = $sUrl . "&itopversion=" . ITOP_VERSION;
}
return $sUrl;
}));
}
/**
@@ -560,7 +578,7 @@ class ApplicationHelper
else
{
$bFound = false;
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $sParentClass)
{
if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
{

View File

@@ -4,7 +4,7 @@
//
// 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.
@@ -23,6 +23,7 @@ use \Exception;
use \Silex\Application;
use \DOMNodeList;
use \DOMFormatException;
use \UserRights;
use \DBObject;
use \DBSearch;
use \DBObjectSet;
@@ -42,6 +43,7 @@ class ContextManipulatorHelper
const ENUM_RULE_CALLBACK_OPEN_EDIT = 'edit';
const DEFAULT_RULE_CALLBACK_OPEN = self::ENUM_RULE_CALLBACK_OPEN_VIEW;
protected $oApp;
protected $aRules;
public function __construct()
@@ -59,7 +61,7 @@ class ContextManipulatorHelper
public function Init(DOMNodeList $oNodes)
{
$this->aRules = array();
// Iterating over the scope nodes
foreach ($oNodes as $oRuleNode)
{
@@ -181,6 +183,11 @@ class ContextManipulatorHelper
}
}
public function SetApp($oApp)
{
$this->oApp = $oApp;
}
/**
* Returns a hash array of rules
*
@@ -222,7 +229,7 @@ class ContextManipulatorHelper
* ...
* )
* )
*
*
* @param array $aData
* @param DBObject $oObject
*/
@@ -290,6 +297,13 @@ class ContextManipulatorHelper
}
}
// Checking for silos
$oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sSearchClass, UR_ACTION_READ);
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
}
// Retrieving source object(s) and applying rules
$oSet = new DBObjectSet($oSearch, array(), $aSearchParams);
while ($oSourceObject = $oSet->Fetch())

View File

@@ -36,6 +36,7 @@ class ScopeValidatorHelper
const ENUM_TYPE_ALLOW = 'allow';
const ENUM_TYPE_RESTRICT = 'restrict';
const DEFAULT_GENERATED_CLASS = 'PortalScopesValues';
const DEFAULT_IGNORE_SILOS = false;
protected $sCachePath;
protected $sFilename;
@@ -179,6 +180,9 @@ class ScopeValidatorHelper
// Retrieving the edit query
$oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
// Retrieving ignore allowed org flag
$oIgnoreSilosNode = $oScopeNode->GetOptionalElement('ignore_silos');
$bIgnoreSilos = ( ($oIgnoreSilosNode !== null) && ($oIgnoreSilosNode->GetText() === 'true') ) ? true : static::DEFAULT_IGNORE_SILOS;
// Retrieving profiles for the scope
$oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
@@ -221,13 +225,20 @@ class ScopeValidatorHelper
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ][$sOqlViewType]);
$aFilters = array($oExistingFilter, $oViewFilter);
$oResFilter = new DBUnionSearch($aFilters);
// Applying ignore_silos flag on result filter if necessary (As the union will remove it if it is not on all sub-queries)
if ($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]['ignore_silos'] === true)
{
$bIgnoreSilos = true;
}
}
else
{
$oResFilter = $oViewFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ] = array(
$sOqlViewType => $oResFilter->ToOQL()
$sOqlViewType => $oResFilter->ToOQL(),
'ignore_silos' => $bIgnoreSilos
);
// - Edit query
if ($sOqlEdit !== null)
@@ -264,7 +275,8 @@ class ScopeValidatorHelper
$oResFilter = $oEditFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE] = array(
$sOqlViewType => $oResFilter->ToOQL()
$sOqlViewType => $oResFilter->ToOQL(),
'ignore_silos' => $bIgnoreSilos
);
}
}
@@ -273,7 +285,7 @@ class ScopeValidatorHelper
$aProfileClasses[] = $sClass;
}
}
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
// If not, we add them with the same OQL
@@ -295,10 +307,14 @@ class ScopeValidatorHelper
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
foreach ($aTmpProfile as $sType => $sOql)
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
// IF condition is just to skip the 'ignore_silos' flag
if (in_array($sType, array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT)))
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
}
}
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
@@ -471,6 +487,7 @@ class ScopeValidatorHelper
$oSearch = null;
$aAllowSearches = array();
$aRestrictSearches = array();
$bIgnoreSilos = static::DEFAULT_IGNORE_SILOS;
// Checking the default mode
if ($iAction === null)
@@ -498,6 +515,11 @@ class ScopeValidatorHelper
{
$aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
}
// If a profile should ignore allowed org, we set it for all its queries no matter the profile
if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
{
$bIgnoreSilos = true;
}
}
}
@@ -514,10 +536,47 @@ class ScopeValidatorHelper
$oSearch = new DBUnionSearch($aAllowSearches);
$oSearch = $oSearch->RemoveDuplicateQueries();
}
if ($bIgnoreSilos === true)
{
$oSearch->AllowAllData();
}
return $oSearch;
}
/**
* Returns true if at least one of the $aProfiles has the ignore_silos flag set to true for the $sClass.
*
* @param array $aProfiles
* @param string $sClass
* @return boolean
*/
public function IsAllDataAllowedForScope($aProfiles, $sClass)
{
$bIgnoreSilos = false;
// Iterating on profiles to retrieving the different OQLs parts
foreach ($aProfiles as $sProfile)
{
// Retrieving matrix informtions
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
// Retrieving profile OQLs
$sScopeValuesClass = $this->sGeneratedClass;
$aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, static::ENUM_MODE_READ);
if ($aProfileMatrix !== null)
{
// If a profile should ignore allowed org, we set it for all its queries no matter the profile
if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
{
$bIgnoreSilos = true;
}
}
}
return $bIgnoreSilos;
}
/**
* Returns the profile id from a string being either a constant or its name.
*

View File

@@ -112,7 +112,7 @@ class SecurityHelper
// Checking if the cmdbAbstractObject exists if id is specified
if ($sObjectId !== null)
{
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
if ($oObject === null)
{
if ($oApp['debug'])

View File

@@ -44,6 +44,7 @@ class UrlGenerator extends SymfonyUrlGenerator
*/
public function generate($name, $parameters = array(), $referenceType = SymfonyUrlGenerator::ABSOLUTE_PATH)
{
// Mandatory parameters
$sExecModule = utils::ReadParam('exec_module', '', false, 'string');
$sExecPage = utils::ReadParam('exec_page', '', false, 'string');
if ($sExecModule !== '' && $sExecPage !== '')
@@ -52,6 +53,18 @@ class UrlGenerator extends SymfonyUrlGenerator
$parameters['exec_page'] = $sExecPage;
}
// Optional parameters
$sEnvSwitch = utils::ReadParam('env_switch', '', false, 'string');
if ($sEnvSwitch !== '')
{
$parameters['env_switch'] = $sEnvSwitch;
}
$sDebug = utils::ReadParam('debug', '', false, 'string');
if ($sDebug !== '')
{
$parameters['debug'] = $sDebug;
}
return parent::generate($name, $parameters, $referenceType);
}

View File

@@ -38,6 +38,7 @@ class ContextManipulatorServiceProvider implements ServiceProviderInterface
$oApp->flush();
$oContextManipulatorHelper = new ContextManipulatorHelper();
$oContextManipulatorHelper->SetApp($oApp);
return $oContextManipulatorHelper;
});

View File

@@ -73,11 +73,11 @@
// For the same reason, tooltip widget is created in "drawCallback" instead of here.
if( (data.tooltip !== undefined) && (data.tooltip !== ''))
{
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').text(data.name).prop('outerHTML') );
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').html(data.name).prop('outerHTML') );
}
else
{
cellElem.text(data.name);
cellElem.html(data.name);
}
// Building actions
@@ -97,11 +97,11 @@
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+levelPrimaryAction.type+'" not implemented');
//console.log('Action "'+levelPrimaryAction.type+'" not implemented');
break;
}
@@ -150,11 +150,11 @@
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
url = AddParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+action.type+'" not implemented for secondary action');
//console.log('Action "'+action.type+'" not implemented for secondary action');
break;
}
@@ -201,7 +201,6 @@
"type": "html",
"data": oLevelsProperties[sKey].alias+".fields."+oLevelsProperties[sKey].fields[i].code
});
console.log(oLevelsProperties[sKey].fields[i].visible);
}
}
}
@@ -240,6 +239,7 @@
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::DEFAULT_COUNT_PER_PAGE_LIST') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
"columns": getColumnsDefinition(),
"order": [],
"drawCallback": function(settings){
// Tooltip has to been created here, as the render callback only returns a string, not an object.
$(this).find('[data-toggle="tooltip"]').tooltip({container: 'body', html: true, trigger: 'hover', placement: 'right'}); // container option is necessary when in a table

View File

@@ -25,7 +25,7 @@
</div>
</div>
<div class="col-xs-8 col-sm-10 col-lg-11 text-right">
<label>Filtrer :<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table" value="{{ sSearchValue }}"></label>
<label>{{ 'Portal:Datatables:Language:Search'|dict_s }}<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table" value="{{ sSearchValue }}"></label>
</div>
</div>
<ul class="list-group" id="brick_content_tree" data-level-id="L">
@@ -223,11 +223,11 @@
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
//console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
break;
}
@@ -275,11 +275,11 @@
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+action.type+'" not implemented for secondary action');
//console.log('Action "'+action.type+'" not implemented for secondary action');
break;
}

View File

@@ -17,24 +17,35 @@
{% endblock %}
{% block pMainContentHolder%}
{% set iTableCount = 0 %}
{% if aGroupingAreasData|length > 0 %}
{% for aAreaData in aGroupingAreasData %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
{% if aAreaData.iItemsCount > 0 %}
{% set iTableCount = iTableCount + 1 %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
</div>
<div class="panel-body">
{# We decided not to show empty tables anymore #}
{#
{% if aAreaData.iItemsCount > 0 %}
#}
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
{#
{% else %}
<div class="text-center">
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
</div>
{% endif %}
#}
</div>
</div>
<div class="panel-body">
{% if aAreaData.iItemsCount > 0 %}
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
{% else %}
<div class="text-center">
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
</div>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
{% endif %}
{% if iTableCount == 0 %}
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
@@ -69,12 +80,12 @@
{
var tableProperties = columnsProperties[tableName];
if(tableProperties === undefined)
if(tableProperties === undefined && window.console)
{
console.log('Could not retrieve columns properties for table "'+tableName+'"');
return false;
}
if(rawData[tableName] === undefined)
if(rawData[tableName] === undefined && window.console)
{
console.log('Could not retrieve data for table "'+tableName+'"');
return false;
@@ -103,7 +114,7 @@
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.text(row.attributes[att_code].value);
cellElem.html(row.attributes[att_code].value);
// Building actions
if(itemActions.length > 0)
{
@@ -120,7 +131,7 @@
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+itemPrimaryAction+'" not implemented');
//console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
}
@@ -227,7 +238,6 @@
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#table-{{ sAreaId }}_filter input').off().on('keyup', function(){
var me = this;
console.log('here');
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function() {
oTable{{ sAreaId }}.search(me.value.latinise()).draw();

View File

@@ -24,7 +24,7 @@
{% if form.buttons is defined and form.buttons.transitions is defined and form.buttons.transitions|length > 0 %}
<div class="form_btn_transitions">
{% for sStimulusCode, sStimulusLabel in form.buttons.transitions %}
<button class="btn btn-default form_btn_transition" type="submit" name="stimulus_code" value="{{ sStimulusCode }}">{{ sStimulusLabel }}</button>
<button class="btn btn-primary form_btn_transition" type="submit" name="stimulus_code" value="{{ sStimulusCode }}">{{ sStimulusLabel }}</button>
{% endfor %}
</div>
{% endif %}
@@ -67,60 +67,63 @@
// Sticky buttons handler
{% if sMode != 'view' %}
// Note : This pattern if to prevent performance issues
// - Cloning buttons
var oNormalRegularButtons_{{ sFormIdSanitized }} = $('#{{ sFormId }} .form_btn_regular');
var oStickyRegularButtons_{{ sFormIdSanitized }} = oNormalRegularButtons_{{ sFormIdSanitized }}.clone(true, true);
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('sticky');
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon').length > 0)
if( $('#{{ sFormId }} .form_btn_regular button').length > 0 )
{
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon')[0].outerHTML );
}
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon').length > 0)
{
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon')[0].outerHTML );
}
$('#{{ sFormId }}').closest({% if tIsModal == true %}'.modal'{% else %}'#main-content'{% endif %}).append(oStickyRegularButtons_{{ sFormIdSanitized }});
// - Global timeout for any
var oScrollTimeout;
// - Scroll handler
scrollHandler_{{ sFormIdSanitized }} = function () {
if($('#{{ sFormId }} .form_buttons').visible())
// Note : This pattern if to prevent performance issues
// - Cloning buttons
var oNormalRegularButtons_{{ sFormIdSanitized }} = $('#{{ sFormId }} .form_btn_regular');
var oStickyRegularButtons_{{ sFormIdSanitized }} = oNormalRegularButtons_{{ sFormIdSanitized }}.clone(true, true);
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('sticky');
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon').length > 0)
{
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('closed');
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_submit span.glyphicon')[0].outerHTML );
}
else
if(oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon').length > 0)
{
oStickyRegularButtons_{{ sFormIdSanitized }}.removeClass('closed');
oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel').html( oStickyRegularButtons_{{ sFormIdSanitized }}.find('.form_btn_cancel span.glyphicon')[0].outerHTML );
}
};
// - Event binding for scroll
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('scroll').on('scroll', function () {
if (oScrollTimeout) {
// Clear the timeout, if one is pending
clearTimeout(oScrollTimeout);
oScrollTimeout = null;
}
oScrollTimeout = setTimeout(scrollHandler_{{ sFormIdSanitized }}, 50);
});
// - Event binding for linkedset collapse
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('shown.bs.collapse hidden.bs.collapse').on('shown.bs.collapse hidden.bs.collapse', function () {
scrollHandler_{{ sFormIdSanitized }}();
});
// - Event binding for form building / updating
// Note : We do not want to 'off' the event or it will remove listeners from the widget
oFieldSet_{{ sFormIdSanitized }}.on('form_built', function(oEvent){
scrollHandler_{{ sFormIdSanitized }}();
});
// - Initial test
setTimeout(function(){ scrollHandler_{{ sFormIdSanitized }}(); }, 400);
// Remove sticky button when closing modal
$('#{{ sFormId }}').closest('.modal').on('hide.bs.modal', function () {
oStickyRegularButtons_{{ sFormIdSanitized }}.remove();
});
$('#{{ sFormId }}').closest({% if tIsModal == true %}'.modal'{% else %}'#main-content'{% endif %}).append(oStickyRegularButtons_{{ sFormIdSanitized }});
// - Global timeout for any
var oScrollTimeout;
// - Scroll handler
scrollHandler_{{ sFormIdSanitized }} = function () {
if($('#{{ sFormId }} .form_buttons').visible())
{
oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('closed');
}
else
{
oStickyRegularButtons_{{ sFormIdSanitized }}.removeClass('closed');
}
};
// - Event binding for scroll
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('scroll').on('scroll', function () {
if (oScrollTimeout) {
// Clear the timeout, if one is pending
clearTimeout(oScrollTimeout);
oScrollTimeout = null;
}
oScrollTimeout = setTimeout(scrollHandler_{{ sFormIdSanitized }}, 50);
});
// - Event binding for linkedset collapse
$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('shown.bs.collapse hidden.bs.collapse').on('shown.bs.collapse hidden.bs.collapse', function () {
scrollHandler_{{ sFormIdSanitized }}();
});
// - Event binding for form building / updating
// Note : We do not want to 'off' the event or it will remove listeners from the widget
oFieldSet_{{ sFormIdSanitized }}.on('form_built', function(oEvent){
scrollHandler_{{ sFormIdSanitized }}();
});
// - Initial test
setTimeout(function(){ scrollHandler_{{ sFormIdSanitized }}(); }, 400);
// Remove sticky button when closing modal
$('#{{ sFormId }}').closest('.modal').on('hide.bs.modal', function () {
oStickyRegularButtons_{{ sFormIdSanitized }}.remove();
});
}
{% endif %}
{% if tIsModal == true %}

View File

@@ -92,7 +92,7 @@
$(document).ready(function(){
showTableLoader();
// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
// We would just have to override / complete the necessary elements
oTable = $('#{{ sTableId }}').DataTable({
@@ -152,6 +152,8 @@
"url": "{{ app.url_generator.generate('p_object_search_from_attribute', {'sTargetAttCode': sTargetAttCode, 'sHostObjectClass': sHostObjectClass, 'sHostObjectId': sHostObjectId, 'ar_token': sActionRulesToken})|raw }}",
"type": "POST",
"data": function(d){
d.sFormPath = '{{ aSource.sFormPath }}';
d.sFieldId = '{{ aSource.sFieldId }}';
d.aObjectIdsToIgnore = {{ aSource.aObjectIdsToIgnore|json_encode()|raw }};
d.iPageNumber = Math.floor(d.start/d.length) + 1;
d.iCountPerPage = d.length;
@@ -168,6 +170,24 @@
// Retrieving values from source form
d.current_values = $('[data-form-path="{{aSource.sFormPath}}"][data-field-id="{{aSource.sFieldId}}"]').closest('.portal_form_handler').portal_form_handler('getCurrentValues');
{% endif %}
},
"error": function(oData, sError, sThrow){
if(oData.responseJSON !== undefined && oData.responseJSON !== null)
{
var oResponse = oData.responseJSON;
// If we encounter an error
if(oResponse.exception !== undefined)
{
// Note : This could be refactored for a global use
$('#{{ sTableId }}').closest('.modal').html( $('#modal-for-alert').html() );
var oModalElem = $('#{{ sTableId }}').closest('.modal');
oModalElem.find('.modal-title').html(oResponse.error_title);
oModalElem.find('.modal-body .alert').html(oResponse.error_message)
.removeClass('alert-success alert-info alert-warning alert-danger')
.addClass('alert-danger');
oModalElem.modal('show');
}
}
}
}
});

View File

@@ -8,7 +8,7 @@
{% if form.buttons is defined and form.buttons.links is defined %}
<div class="form_btn_transitions">
{% for aLink in form.buttons.links %}
<a class="btn btn-default" href="{{ aLink.url }}">{{ aLink.label }}</a>
<a class="btn btn-primary" href="{{ aLink.url }}">{{ aLink.label }}</a>
{% endfor %}
</div>
{% endif %}

View File

@@ -43,7 +43,7 @@
<div class="col-sm-6">
<div class="panel panel-default user_profile_picture">
<div class="panel-heading">
<h3 class="panel-title">Photo</h3>
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Photo:Title'|dict_s }}</h3>
</div>
<div class="panel-body" style="position: relative;">
<div class="form_alerts">
@@ -189,11 +189,11 @@
});
// - Undo button
/*$('#user-profile-wrapper .actions .btn_undo').on('click', function(oEvent){
console.log('Picture undo trigger');
//console.log('Picture undo trigger');
});*/
// - Reset button
$('#user-profile-wrapper .actions .btn_reset').on('click', function(oEvent){
console.log('Picture reset trigger');
//console.log('Picture reset trigger');
});
// Submit button

View File

@@ -0,0 +1,6 @@
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>

View File

@@ -22,83 +22,84 @@
{# This block can be used to add your own meta tags by extending the default template #}
{% block pPageExtraMetas %}
{% endblock %}
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - iTop{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] }}images/favicon.ico?itopversion=$ITOP_VERSION$" />
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - {{ constant('ITOP_APPLICATION') }}{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] ~ 'images/favicon.ico'|add_itop_version }}" />
{% block pPageStylesheets %}
{# First bootstrap core, lib themes, then bootstrap theme, portal adjustements #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/css/bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
{# - Bootstrap Datetime picker #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css'|add_itop_version }}" rel="stylesheet">
{# - Datatables #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/dataTables.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/fixedHeader.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/responsive.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/scroller.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.dataTables.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/dataTables.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/fixedHeader.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/responsive.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/scroller.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/select.bootstrap.min.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/css/select.dataTables.min.css'|add_itop_version }}" rel="stylesheet">
{# - Font Combodo #}
<link href="{{ app['combodo.absolute_url'] }}css/font-combodo/font-combodo.css" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ 'css/font-combodo/font-combodo.css'|add_itop_version }}" rel="stylesheet">
{# - Font awesome #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/font-awesome/css/font-awesome.min.css'|add_itop_version }}" rel="stylesheet">
{# - Misc libs #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/css/typeaheadjs.bootstrap.css" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] }}css/magnific-popup.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/css/typeaheadjs.bootstrap.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ 'css/magnific-popup.css'|add_itop_version }}" rel="stylesheet">
{# - Bootstrap theme #}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap }}" rel="stylesheet" id="css_bootstrap_theme">
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap|add_itop_version }}" rel="stylesheet" id="css_bootstrap_theme">
{# - Portal adjustments for BS theme #}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal }}" rel="stylesheet" id="css_portal">
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal|add_itop_version }}" rel="stylesheet" id="css_portal">
{# Custom CSS that is supposed to do adjustments to the portal #}
{% if app['combodo.portal.instance.conf'].properties.themes.custom is defined %}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom }}" rel="stylesheet">
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom|add_itop_version }}" rel="stylesheet">
{% endif %}
{# Others CSS that will come after the theme/portal/custom, in an undefined order #}
{% if app['combodo.portal.instance.conf'].properties.themes.others is defined %}
{% for theme in app['combodo.portal.instance.conf'].properties.themes.others %}
<link href="{{ theme }}" rel="stylesheet">
<link href="{{ theme|add_itop_version }}" rel="stylesheet">
{% endfor %}
{% endif %}
{% endblock %}
{% block pPageScripts %}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery-ui-1.10.3.custom.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.magnific-popup.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.fileupload.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/latinise/latinise.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery/jquery-1.11.3.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery-ui-1.10.3.custom.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.magnific-popup.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.iframe-transport.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.fileupload.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/js/bootstrap.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/latinise/latinise.min.js'|add_itop_version }}"></script>
{# Visible.js to check if an element is visible on screen #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-visible/js/jquery.visible.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-visible/js/jquery.visible.min.js'|add_itop_version }}"></script>
{# Base64.js #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-base64/js/jquery.base64.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-base64/js/jquery.base64.min.js'|add_itop_version }}"></script>
{# Moment.js #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/moment/js/moment.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/moment/js/moment.min.js'|add_itop_version }}"></script>
{# Datatables #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.bootstrap.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.fixedHeader.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.responsive.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.scroller.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.select.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/datetime-moment.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/dataTables.accentNeutraliseForFilter.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/jquery.dataTables.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.bootstrap.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.fixedHeader.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.responsive.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.scroller.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/dataTables.select.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/datatables/js/datetime-moment.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/dataTables.accentNeutraliseForFilter.js'|add_itop_version }}"></script>
{# CKEditor files for HTML WYSIWYG #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/adapters/jquery.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/ckeditor/ckeditor.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/ckeditor/adapters/jquery.js'|add_itop_version }}"></script>
{# Date-time picker for Bootstrap #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js'|add_itop_version }}"></script>
{# Typeahead files for autocomplete #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/bloodhound.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.bundle.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.jquery.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/handlebars/js/handlebars.min-768ddbd.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/bloodhound.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/typeahead.bundle.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/typeahead.jquery.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/handlebars/js/handlebars.min-768ddbd.js'|add_itop_version }}"></script>
{# Form files #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_handler.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/subform_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/field_set.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_handler.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_field.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/subform_field.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/field_set.js'|add_itop_version }}"></script>
{# Form files for portal #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_handler.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field_html.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_handler.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field_html.js'|add_itop_version }}"></script>
{% endblock %}
</head>
<body class="{% block pPageBodyClass %}{% endblock %}">
@@ -260,12 +261,7 @@
<div class="modal fade" id="modal-for-all" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
</div>
</div>
</div>
@@ -289,12 +285,7 @@
<div id="page_overlay" class="global_overlay">
<div class="overlay_content">
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
</div>
</div>
{% endblock %}
@@ -305,19 +296,22 @@
{
return '{{ app['combodo.absolute_url'] }}';
};
var addParameterToUrl = function(sUrl, sParamName, sParamValue)
var AddParameterToUrl = function(sUrl, sParamName, sParamValue)
{
sUrl += (sUrl.split('?')[1] ? '&':'?') + sParamName + '=' + sParamValue;
return sUrl;
};
var contentLoaderTemplate = '<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>';
var GetContentLoaderTemplate = function()
{
return '<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>';
}
$(document).ready(function(){
{% block pPageReadyScripts %}
// Hack to enable a same modal to load content from different urls
$('body').on('hidden.bs.modal', '.modal#modal-for-all', function () {
$(this).removeData('bs.modal');
$(this).find('.modal-content').html(contentLoaderTemplate);
$(this).find('.modal-content').html(GetContentLoaderTemplate());
});
// Hack to enable multiple modals by making sure the .modal-open class is set to the <body> when there is at least one modal open left
$('body').on('hidden.bs.modal', function () {
@@ -335,7 +329,7 @@
$('body').on('loaded.bs.modal', function (oEvent) {
var sModalContent = $(oEvent.target).find('.modal-content').html();
if( (sModalContent === '') || (sModalContent.replace(/[\n\r\t]+/g, '') === contentLoaderTemplate) )
if( (sModalContent === '') || (sModalContent.replace(/[\n\r\t]+/g, '') === GetContentLoaderTemplate()) )
{
$(oEvent.target).find('.modal-content').html($('#modal-for-alert .modal-content').html());
$(oEvent.target).find('.modal-content .modal-header .modal-title').text('{{ 'Error:HTTP:500'|dict_s }}');

View File

@@ -1,12 +1,14 @@
{# modal/layout.html.twig #}
{# Base modal layout, used to fill Bootstrap .modal-content #}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">{% block pModalTitle %}{% endblock %}</h4>
</div>
<div class="modal-body">{% block pModalBody %}{% endblock %}</div>
<div class="modal-footer">
{% block pModalFooter %}
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:ButtonClose'|dict_s }}</button>
{% endblock %}
</div>
{% block pModalContent %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">{% block pModalTitle %}{% endblock %}</h4>
</div>
<div class="modal-body">{% block pModalBody %}{% endblock %}</div>
<div class="modal-footer">
{% block pModalFooter %}
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:ButtonClose'|dict_s }}</button>
{% endblock %}
</div>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_loader.html.twig #}
{# Modal loader layout #}
{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
{% block pModalContent %}
{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
{% if redirection is defined and redirection.url is defined %}
<script type="text/javascript">
$(document).ready( function(){
window.location = '{{ redirection.url|raw }}';
});
</script>
{% endif %}
{% endblock %}

View File

@@ -267,6 +267,10 @@ footer {
.mfp-wrap {
z-index: 1210;
}
.mfp-img {
cursor: pointer;
cursor: zoom-out;
}
/********************/
/* Typeahed setting */
/********************/
@@ -438,6 +442,10 @@ footer {
.dataTables_wrapper {
padding: 10px 10px;
}
.dataTable.table td img {
max-width: 100%;
height: initial !important;
}
#brick_content_toolbar {
/* margin: 10px 0px 6px 0px; */
padding: 10px;
@@ -602,6 +610,11 @@ table .group-actions {
color: #ea7d1e;
font-size: 0.9em;
}
/* InlineImage */
.inline-image {
cursor: pointer;
cursor: zoom-in;
}
/* CaseLog field */
.caselog_field_entry {
border: 1px solid #ddd;
@@ -622,6 +635,7 @@ table .group-actions {
font-size: 16px;
border: 1px solid #a6a6a6;
border-bottom-color: #979797;
cursor: pointer;
}
.caselog_field_entry_button:hover {
background-color: #ccc;
@@ -796,6 +810,10 @@ table .group-actions {
}
}
}
/* BlobField */
.form_fields .file_open_link {
margin-left: 10px;
}
.form_field .form-control-static img {
max-width: 100% !important;
height: initial !important;
@@ -816,7 +834,8 @@ table .group-actions {
@media (min-width: 768px) {
/* Making regular button sticky */
.form_buttons .form_btn_transitions {
float: left !important;
float: right !important;
margin-left: 3px;
}
.form_buttons .form_btn_regular {
text-align: right;
@@ -885,9 +904,17 @@ table .group-actions {
border-color: #fbeed5;
color: #c09853;
}
/* CKEditor : Misc */
.cke_toolbox_collapser, .cke_toolbox_collapser .cke_arrow {
cursor: pointer !important;
}
/* DataTables : Selection inputs */
.dataTable.table th span.row_input, .dataTable.table td span.row_input {
display: inline-block;
width: 100%;
text-align: center;
}
/* Wiki text (hyperlinks) */
.wiki_broken_link {
text-decoration: line-through;
}

View File

@@ -282,6 +282,10 @@ footer{
.mfp-wrap{
z-index: 1210;
}
.mfp-img{
cursor: pointer;
cursor: zoom-out;
}
/********************/
/* Typeahed setting */
@@ -461,6 +465,10 @@ footer{
.dataTables_wrapper{
padding: 10px 10px;
}
.dataTable.table td img{
max-width: 100%;
height: initial !important;
}
#brick_content_toolbar{
/* margin: 10px 0px 6px 0px; */
padding: 10px;
@@ -640,6 +648,11 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
color: $brand-primary;
font-size: 0.9em;
}
/* InlineImage */
.inline-image{
cursor: pointer;
cursor: zoom-in;
}
/* CaseLog field */
.caselog_field_entry{
border: 1px solid $gray-lighter;
@@ -660,6 +673,7 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
font-size: 16px;
border: 1px solid #a6a6a6;
border-bottom-color: #979797;
cursor: pointer;
}
.caselog_field_entry_button:hover{
background-color: #cccccc;
@@ -836,6 +850,10 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
}
}
}
/* BlobField */
.form_fields .file_open_link{
margin-left: 10px;
}
.form_field .form-control-static img{
max-width: 100% !important;
@@ -857,7 +875,8 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
}
@media (min-width: 768px){
.form_buttons .form_btn_transitions{
float: left !important;
float: right !important;
margin-left: 3px;
}
.form_buttons .form_btn_regular{
text-align: right;
@@ -927,6 +946,11 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
border-color: $alert-warning-border;
color: $alert-warning-text;
}
/* CKEditor : Misc */
.cke_toolbox_collapser,
.cke_toolbox_collapser .cke_arrow{
cursor: pointer !important;
}
/* DataTables : Selection inputs */
.dataTable.table th span.row_input,
@@ -934,4 +958,8 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
display: inline-block;
width: 100%;
text-align: center;
}
/* Wiki text (hyperlinks) */
.wiki_broken_link {
text-decoration: line-through;
}

View File

@@ -62,6 +62,7 @@ if (!defined('DISABLE_DATA_LOCALIZER_PORTAL'))
$bDebug = (isset($_REQUEST['debug']) && ($_REQUEST['debug'] === 'true') );
// Initializing Silex framework
$oKPI = new ExecutionKPI();
$oApp = new Silex\Application();
// Registring optional silex components
@@ -76,6 +77,7 @@ $oApp->register(new Silex\Provider\TwigServiceProvider(), array(
'twig.path' => MODULESROOT
));
$oApp->register(new Silex\Provider\HttpFragmentServiceProvider());
$oKPI->ComputeAndReport('Initialization of the Silex application');
// Configuring Silex application
$oApp['debug'] = $bDebug;
@@ -91,17 +93,27 @@ $oApp['combodo.portal.instance.routes'] = array();
ApplicationHelper::RegisterExceptionHandler($oApp);
// Preparing portal foundations (Can't use Silex autoload through composer as we don't follow PSR conventions -filenames, functions-)
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadControllers();
ApplicationHelper::LoadRouters();
ApplicationHelper::RegisterRoutes($oApp);
ApplicationHelper::LoadBricks();
ApplicationHelper::LoadFormManagers();
ApplicationHelper::RegisterTwigExtensions($oApp);
$oKPI->ComputeAndReport('Loading portal files (routers, controllers, ...)');
// Loading portal configuration from the module design
$oKPI = new ExecutionKPI();
ApplicationHelper::LoadPortalConfiguration($oApp);
$oKPI->ComputeAndReport('Parsing portal configuration');
// Loading current user
ApplicationHelper::LoadCurrentUser($oApp);
// Running application
$oKPI = new ExecutionKPI();
$oApp->run();
$oKPI->ComputeAndReport('Page execution and rendering');
// Logging trace and stats
DBSearch::RecordQueryTrace();
ExecutionKPI::ReportStats();

View File

@@ -171,7 +171,13 @@ $(function()
oModalElem.attr('id', '').appendTo('body');
// Loading content
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
oModalElem.find('.modal-content').load(sUrl);
oModalElem.find('.modal-content').load(sUrl, {
// Passing formmanager data to the next page, just in case it needs it (eg. when applying stimulus)
formmanager_class: me.options.formmanager_class,
formmanager_data: JSON.stringify(me.options.formmanager_data)
}
);
oModalElem.modal('show');
}
else

View File

@@ -37,4 +37,5 @@ $sDir = basename(__DIR__);
define('PORTAL_MODULE_ID', $sDir);
define('PORTAL_ID', $sDir);
ApplicationContext::SetUrlMakerClass('iTopPortalViewUrlMaker');
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/index.php';

View File

@@ -22,20 +22,26 @@
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class iTopPortalUrlMaker implements iDBObjectURLMaker
class iTopPortalEditUrlMaker implements iDBObjectURLMaker
{
public static function MakeObjectURL($sClass, $iId)
/**
* Generate an (absolute) URL to an object, either in view or edit mode
* @param string $sClass The class of the object
* @param int $iId The identifier of the object
* @param string $sMode edit|view
* @return string
*/
public static function PrepareObjectURL($sClass, $iId, $sMode)
{
require_once APPROOT . '/lib/silex/vendor/autoload.php';
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/providers/urlgeneratorserviceprovider.class.inc.php';
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/helpers/urlgeneratorhelper.class.inc.php';
require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/src/helpers/applicationhelper.class.inc.php';
// Using a static var allows to preserve the object through function calls
static $oApp = null;
static $sPortalId = null;
// Initializing Silex app
if ($oApp === null)
{
@@ -49,15 +55,51 @@ class iTopPortalUrlMaker implements iDBObjectURLMaker
// Retrieving portal id
$sPortalId = basename(__DIR__);
}
$sObjectQueryString = $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
$sPortalAbsoluteUrl = utils::GetAbsoluteUrlModulePage($sPortalId, 'index.php');
$sUrl = str_replace('?', $sObjectQueryString . '?', $sPortalAbsoluteUrl);
// The object is reachable in the specified mode (edit/view)
switch($sMode)
{
case 'view':
$sObjectQueryString = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
break;
case 'edit':
default:
$sObjectQueryString = $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sClass, 'sObjectId' => $iId));
}
$sPortalAbsoluteUrl = utils::GetAbsoluteUrlModulePage($sPortalId, 'index.php');
if (strpos($sPortalAbsoluteUrl, '?') !== false)
{
$sUrl = substr($sPortalAbsoluteUrl, 0, strpos($sPortalAbsoluteUrl, '?')).$sObjectQueryString.substr($sPortalAbsoluteUrl, strpos($sPortalAbsoluteUrl, '?'));
}
else
{
$sUrl = $sPortalAbsoluteUrl.$sObjectQueryString;
}
return $sUrl;
}
public static function MakeObjectURL($sClass, $iId)
{
return static::PrepareObjectURL($sClass, $iId, 'edit');
}
}
DBObject::RegisterURLMakerClass('portal', 'iTopPortalUrlMaker');
/**
* Hyperlinks to the "view" of the object (vs edition)
* @author denis
*
*/
class iTopPortalViewUrlMaker extends iTopPortalEditUrlMaker
{
public static function MakeObjectURL($sClass, $iId)
{
return static::PrepareObjectURL($sClass, $iId, 'view');
}
}
// Default portal hyperlink (for notifications) is the edit hyperlink
DBObject::RegisterURLMakerClass('portal', 'iTopPortalEditUrlMaker');

View File

@@ -1325,7 +1325,7 @@
<code><![CDATA[ public function UpdateChildRequestLog()
{
$oLog = $this->Get('public_log');
$sLogPublic = $oLog->GetModifiedEntry();
$sLogPublic = $oLog->GetModifiedEntry('html');
if ($sLogPublic != '')
{
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
@@ -1343,7 +1343,7 @@
}
$oLog = $this->Get('private_log');
$sLogPrivate = $oLog->GetModifiedEntry();
$sLogPrivate = $oLog->GetModifiedEntry('html');
if ($sLogPrivate != '')
{
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";

View File

@@ -1338,7 +1338,7 @@
<code><![CDATA[ public function UpdateChildRequestLog()
{
$oLog = $this->Get('public_log');
$sLogPublic = $oLog->GetModifiedEntry();
$sLogPublic = $oLog->GetModifiedEntry('html');
if ($sLogPublic != '')
{
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
@@ -1356,7 +1356,7 @@
}
$oLog = $this->Get('private_log');
$sLogPrivate = $oLog->GetModifiedEntry();
$sLogPrivate = $oLog->GetModifiedEntry('html');
if ($sLogPrivate != '')
{
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";

View File

@@ -698,7 +698,7 @@
<code><![CDATA[ public function UpdateParentTicketLog()
{
$oLog = $this->Get('log');
$sLog = $oLog->GetModifiedEntry();
$sLog = $oLog->GetModifiedEntry('html');
if ($sLog != '')
{
$oTicket = MetaModel::GetObject('Ticket', $this->Get('ticket_id'), false);
@@ -998,7 +998,7 @@
<default>fa fa-user fa-2x</default>
</decoration_class>
<form>
<!-- Optionnal tag to list the fields -->
<!-- Optionnal tag to list the fields. If empty only fields from <twig> tag will be displayed, if ommited fields from zlist details will. -->
<fields />
<!-- Optionnal tag to specify the form layout. Fields that are not positionned in the layout will be placed at the end of the form -->
<twig>
@@ -1052,7 +1052,7 @@
<!-- Description text from attribute of above class [from the OQL] -->
<tooltip_att>description</tooltip_att>
<!-- Title of the level, will be display in lists and others browse modes -->
<title>Service</title>
<title>Class:Service</title>
<!-- Optional tag to add attributes to the table by their code, can be specified for each level -->
<!-- <fields /> -->
<!-- Can be empty on intermediate levels, default is drilldown -->
@@ -1066,7 +1066,7 @@
<parent_att>service_id</parent_att>
<name_att/>
<tooltip_att>description</tooltip_att>
<title>Sous-Service</title>
<title>Class:ServiceSubcategory</title>
<actions>
<action id="view" xsi:type="view"/>
<action id="create_from_this" xsi:type="create_from_this">
@@ -1130,9 +1130,9 @@
</fields>
<!-- Optional tag to add attributes to the table by their code -->
<grouping>
<!-- Optionnal -->
<!-- Mandatory -->
<tabs>
<!-- Optionnal. Grouping by tabs -->
<!-- Mandatory. Grouping by tabs -->
<!--<attribute>operational_status</attribute>-->
<!-- attribute xor groups tag -->
<groups>
@@ -1145,7 +1145,7 @@
<group id="resolved">
<rank>2</rank>
<title>Brick:Portal:OngoingRequests:Tab:Resolved</title>
<condition><![CDATA[SELECT Ticket AS T WHERE org_id = :current_contact->org_id AND operational_status = 'resolved']]></condition>
<condition><![CDATA[SELECT Ticket AS T WHERE operational_status = 'resolved']]></condition>
</group>
</groups>
</tabs>
@@ -1169,7 +1169,7 @@
<decoration_class>
<default>fc fc-closed-request fc-2x</default>
</decoration_class>
<oql><![CDATA[SELECT UserRequest WHERE org_id = :current_contact->org_id AND caller_id = :current_contact_id AND status = 'closed']]></oql>
<oql><![CDATA[SELECT Ticket WHERE org_id = :current_contact->org_id AND caller_id = :current_contact_id AND operational_status = 'closed']]></oql>
<!-- Can be either a class tag with the class name or an oql tag with the query -->
<!-- <class>Ticket</class> -->
<fields>
@@ -1181,6 +1181,18 @@
<field id="priority"/>
<field id="caller_id"/>
</fields>
<grouping>
<tabs>
<groups>
<group id="all">
<rank>1</rank>
<title>Brick:Portal:ClosedRequests:Title</title>
<condition><![CDATA[SELECT Ticket]]></condition>
</group>
</groups>
</tabs>
<!-- Implicit grouping on y axis by finalclass -->
</grouping>
<data_loading>auto</data_loading>
</brick>
</bricks>
@@ -1309,6 +1321,21 @@
<mode id="view"/>
</modes>
</form>
<form id="ticket-apply-stimulus">
<class>Ticket</class>
<fields />
<twig>
<div>
<div class="form_field" data-field-id="team_id" data-field-flags="read_only">
</div>
<div class="form_field" data-field-id="agent_id" data-field-flags="read_only">
</div>
</div>
</twig>
<modes>
<mode id="apply_stimulus"/>
</modes>
</form>
<form id="service-view">
<class>Service</class>
<fields></fields>
@@ -1359,6 +1386,13 @@
</scope>
</scopes>
</class>
<class id="Location">
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT Location WHERE org_id = :current_contact->org_id]]></oql_view>
</scope>
</scopes>
</class>
<class id="Contact">
<scopes>
<scope id="all">
@@ -1391,6 +1425,7 @@
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT ServiceFamily AS sf JOIN Service AS s ON s.servicefamily_id = sf.id JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id]]></oql_view>
<ignore_silos>true</ignore_silos>
</scope>
</scopes>
</class>
@@ -1398,6 +1433,7 @@
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND s.status != 'obsolete']]></oql_view>
<ignore_silos>true</ignore_silos>
</scope>
</scopes>
</class>
@@ -1405,6 +1441,7 @@
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT ServiceSubcategory AS ssc JOIN Service AS s ON ssc.service_id=s.id JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND ssc.status != 'obsolete']]></oql_view>
<ignore_silos>true</ignore_silos>
</scope>
</scopes>
</class>

View File

@@ -185,5 +185,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'Zielvorgabe (SLT) vom Typ TTO',
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'Time To Resolve (Erstlösungszeit)',
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'Zielvorgabe (SLT) vom Typ TTR',
'portal:itop-portal' => 'Standard Portal', // This is the portal name that will be displayed in portal dispatcher (eg. URL in menus)
'Brick:Portal:UserProfile:Title' => 'Mein Profile',
'Brick:Portal:NewRequest:Title' => 'Neue Störung/Anfrage',
'Brick:Portal:NewRequest:Title+' => '<p>Hilfe?</p><p>Wählen Sie einen Service aus und senden Sie Ihre Anfrage zum Service Desk.</p>',
'Brick:Portal:OngoingRequests:Title' => 'Offene Störungen/Anfragen',
'Brick:Portal:OngoingRequests:Title+' => '<p>Hier können Sie Ihre laufenden Anfragen und Störungsmeldungen ansehen,</p><p>den Verlauf verfolgen, Kommentare und Anhänge hinzufügen und gelöste Anfragen schließen.</p>',
'Brick:Portal:OngoingRequests:Tab:OnGoing' => 'Offen',
'Brick:Portal:OngoingRequests:Tab:Resolved' => 'Gelöst',
'Brick:Portal:ClosedRequests:Title' => 'Geschlossene Störungen/Anfragen',
));
?>

View File

@@ -231,6 +231,16 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'Goal based on a SLT of type TTO~~',
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'Time To Resolve~~',
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'Goal based on a SLT of type TTR~~',
'portal:itop-portal' => 'Portal estándar',
'Page:DefaultTitle' => 'ITop - Portal de usuarios',
'Brick:Portal:UserProfile:Title' => 'Mi perfil',
'Brick:Portal:NewRequest:Title' => 'Nueva solicitud',
'Brick:Portal:NewRequest:Title+' => '¿Necesita ayuda? Elija del catálogo de servicios y envíe su solicitud a nuestros equipos de soporte.',
'Brick:Portal:OngoingRequests:Title' => 'Solicitudes en curso',
'Brick:Portal:OngoingRequests:Title+' => 'Revise sus peticiones en curso. Compruebe el progreso, agregue comentarios, adjunte documentos, entienda la solución. </ P>',
'Brick:Portal:OngoingRequests:Tab:OnGoing' => 'En marcha',
'Brick:Portal:OngoingRequests:Tab:Resolved' => 'Resuelto',
'Brick:Portal:ClosedRequests:Title' => 'Solicitudes cerradas',
));

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.
//
@@ -260,7 +260,7 @@ class _Ticket extends cmdbAbstractObject
case 'Contact':
// Only link Contacts which are not already linked to the ticket
if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aCIsToImpactCode[$iKey] != 'do_not_notify'))
if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aContactsToRoleCode[$iKey] != 'do_not_notify'))
{
$oNewLink = new lnkContactToTicket();
$oNewLink->Set('contact_id', $iKey);

View File

@@ -502,11 +502,11 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'UI:ResetPwd-Ready' => 'La contraseña ha sido cambiada.',
'UI:ResetPwd-Login' => 'Click aquí para conectarse ',
'UI:Login:About' => '',
'UI:Login:ChangeYourPassword' => 'Cambie su Contrase&ntilde;a',
'UI:Login:OldPasswordPrompt' => 'Contrase&ntilde;a Actual',
'UI:Login:NewPasswordPrompt' => 'Contrase&ntilde;a Nueva',
'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contrase&ntilde;a Nueva',
'UI:Login:IncorrectOldPassword' => 'Error: la Contrase&ntilde;a Anterior es Incorrecta',
'UI:Login:ChangeYourPassword' => 'Cambie su Contraseña',
'UI:Login:OldPasswordPrompt' => 'Contraseña Actual',
'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva',
'UI:Login:RetypeNewPasswordPrompt' => 'Confirme Contraseña Nueva',
'UI:Login:IncorrectOldPassword' => 'Error: la Contraseña Anterior es Incorrecta',
'UI:LogOffMenu' => 'Cerrar Sesión',
'UI:LogOff:ThankYou' => 'Gracias por usar iTop',
'UI:LogOff:ClickHereToLoginAgain' => 'Click aquí para conectarse nuevamente',

View File

@@ -1,6 +1,125 @@
CKEditor 4 Changelog
====================
## CKEditor 4.6
New Features:
* [#14569](http://dev.ckeditor.com/ticket/14569): Added a new, flat, default CKEditor skin called [Moono-Lisa](http://ckeditor.com/addon/moono-lisa). Refreshed default colors available in the [Color Button](http://ckeditor.com/addon/colorbutton) plugin ([Text Color and Background Color](http://docs.ckeditor.com/#!/guide/dev_colorbutton) feature).
* [#14707](http://dev.ckeditor.com/ticket/14707): Added a new [Copy Formatting](http://ckeditor.com/addon/copyformatting) feature to enable easy copying of styles between your document parts.
* Introduced the completely rewritten [Paste from Word](http://ckeditor.com/addon/pastefromword) plugin:
* Backward incompatibility: The [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) option now defaults to `false`. This option will be deprecated in the future. Use [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_acf) to replicate the effect of setting it to `true`.
* Backward incompatibility: The [`config.pasteFromWordNumberedHeadingToList`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordNumberedHeadingToList) and [`config.pasteFromWordRemoveStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveStyles) options were dropped and no longer have any effect on pasted content.
* Major improvements in preservation of list numbering, styling and indentation (nested lists with multiple levels).
* Major improvements in document structure parsing that fix plenty of issues with distorted or missing content after paste.
* Added new translation: Occitan. Thanks to Cédric Valmary!
* [#10015](http://dev.ckeditor.com/ticket/10015): Keyboard shortcuts (relevant to the operating system in use) will now be displayed in tooltips and context menus.
* [#13794](http://dev.ckeditor.com/ticket/13794): The [Upload Image](http://ckeditor.com/addon/uploadimage) feature now uses `uploaded.width/height` if set.
* [#12541](http://dev.ckeditor.com/ticket/12541): Added the [Upload File](http://ckeditor.com/addon/uploadfile) plugin that lets you upload a file by drag&amp;dropping it into the editor content.
* [#14449](http://dev.ckeditor.com/ticket/14449): Introduced the [Balloon Panel](http://ckeditor.com/addon/balloonpanel) plugin that lets you create stylish floating UI elements for the editor.
* [#12077](https://dev.ckeditor.com/ticket/12077): Added support for the HTML5 `download` attribute in link (`<a>`) elements. Selecting the "Force Download" checkbox in the [Link](http://ckeditor.com/addon/link) dialog will cause the linked file to be downloaded automatically. Thanks to [sbusse](https://github.com/sbusse)!
* [#13518](http://dev.ckeditor.com/ticket/13518): Introduced the [`additionalRequestParameters`](http://docs.ckeditor.com/#!/api/CKEDITOR.fileTools.uploadWidgetDefinition-property-additionalRequestParameters) property for file uploads to make it possible to send additional information about the uploaded file to the server.
* [#14889](http://dev.ckeditor.com/ticket/14889): Added the [`config.image2_altRequired`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_altRequired) option for the [Enhanced Image](http://ckeditor.com/addon/image2) plugin to allow making alternative text a mandatory field. Thanks to [Andrey Fedoseev](https://github.com/andreyfedoseev)!
Fixed Issues:
* [#9991](http://dev.ckeditor.com/ticket/9991): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) should only normalize input data.
* [#7209](http://dev.ckeditor.com/ticket/7209): Fixed: Lists with 3 levels not [pasted from Word](http://ckeditor.com/addon/pastefromword) correctly.
* [#14335](http://dev.ckeditor.com/ticket/14335): Fixed: Pasting a numbered list starting with a value different from "1" from Microsoft Word does not work correctly.
* [#14542](http://dev.ckeditor.com/ticket/14542): Fixed: Copying a numbered list from Microsoft Word does not preserve list formatting.
* [#14544](http://dev.ckeditor.com/ticket/14544): Fixed: Copying a nested list from Microsoft Word results in an empty list.
* [#14660](http://dev.ckeditor.com/ticket/14660): Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) breaks the styling in some cases.
* [#14867](http://dev.ckeditor.com/ticket/14867): [Firefox] Fixed: Text gets stripped when [pasting content from Word](http://ckeditor.com/addon/pastefromword).
* [#2507](http://dev.ckeditor.com/ticket/2507): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) does not detect pasting a part of a paragraph.
* [#3336](http://dev.ckeditor.com/ticket/3336): Fixed: Extra blank row added on top of the content [pasted from Word](http://ckeditor.com/addon/pastefromword).
* [#6115](http://dev.ckeditor.com/ticket/6115): Fixed: When Right-to-Left text direction is applied to a table [pasted from Word](http://ckeditor.com/addon/pastefromword), borders are missing on one side.
* [#6342](http://dev.ckeditor.com/ticket/6342): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) filters out a basic text style when it is [configured to use attributes](http://docs.ckeditor.com/#!/guide/dev_basicstyles-section-custom-basic-text-style-definition).
* [#6457](http://dev.ckeditor.com/ticket/6457): [IE] Fixed: [Pasting from Word](http://ckeditor.com/addon/pastefromword) is extremely slow.
* [#6789](http://dev.ckeditor.com/ticket/6789): Fixed: The `mso-list: ignore` style is not handled properly when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#7262](http://dev.ckeditor.com/ticket/7262): Fixed: Lists in preformatted body disappear when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#7662](http://dev.ckeditor.com/ticket/7662): [Opera] Fixed: Extra empty number/bullet shown in the editor body when editing a multi-level list [pasted from Word](http://ckeditor.com/addon/pastefromword).
* [#7807](http://dev.ckeditor.com/ticket/7807): Fixed: Last item in a list not converted to a `<li>` element after [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#7950](http://dev.ckeditor.com/ticket/7950): [IE] Fixed: Content [from Word pasted](http://ckeditor.com/addon/pastefromword) differently than in other browsers.
* [#7982](http://dev.ckeditor.com/ticket/7982): Fixed: Multi-level lists get split into smaller ones when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#8231](http://dev.ckeditor.com/ticket/8231): [WebKit, Opera] Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts empty paragraphs.
* [#8266](http://dev.ckeditor.com/ticket/8266): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts a blank line at the top.
* [#8341](http://dev.ckeditor.com/ticket/8341), [#7646](http://dev.ckeditor.com/ticket/7646): Fixed: Faulty removal of empty `<span>` elements in [Paste from Word](http://ckeditor.com/addon/pastefromword) content cleanup breaking content formatting.
* [#8754](http://dev.ckeditor.com/ticket/8754): [Firefox] Fixed: Incorrect pasting of multiple nested lists in [Paste from Word](http://ckeditor.com/addon/pastefromword).
* [#8983](http://dev.ckeditor.com/ticket/8983): Fixed: Alignment lost when [pasting from Word](http://ckeditor.com/addon/pastefromword) with [`config.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode) set to [`CKEDITOR.ENTER_BR`](http://docs.ckeditor.com/#!/api/CKEDITOR-property-ENTER_BR).
* [#9331](http://dev.ckeditor.com/ticket/9331): [IE] Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) creates a simple Caesar cipher.
* [#9422](http://dev.ckeditor.com/ticket/9422): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) leaves an unwanted `color:windowtext` style.
* [#10011](http://dev.ckeditor.com/ticket/10011): [IE9-10] Fixed: [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) is ignored under certain conditions.
* [#10643](http://dev.ckeditor.com/ticket/10643): Fixed: Differences between using <kbd>Ctrl+V</kbd> and pasting from the [Paste from Word](http://ckeditor.com/addon/pastefromword) dialog.
* [#10784](http://dev.ckeditor.com/ticket/10784): Fixed: Lines missing when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#11294](http://dev.ckeditor.com/ticket/11294): [IE10] Fixed: Font size is not preserved when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#11627](http://dev.ckeditor.com/ticket/11627): Fixed: Missing words when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#12784](http://dev.ckeditor.com/ticket/12784): Fixed: Bulleted list with custom bullets gets changed to a numbered list when [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#13174](http://dev.ckeditor.com/ticket/13174): Fixed: Data loss after [pasting from Word](http://ckeditor.com/addon/pastefromword).
* [#13828](http://dev.ckeditor.com/ticket/13828): Fixed: Widget classes should be added to the wrapper rather than the widget element.
* [#13829](http://dev.ckeditor.com/ticket/13829): Fixed: No class in [Widget](http://ckeditor.com/addon/widget) wrapper to identify the widget type.
* [#13519](http://dev.ckeditor.com/ticket/13519): Server response received when uploading files should be more flexible.
Other Changes:
* Updated [SCAYT](http://ckeditor.com/addon/scayt) (Spell Check As You Type) and [WebSpellChecker](http://ckeditor.com/addon/wsc) plugins:
* Support for the new default Moono-Lisa skin.
* [#121](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/121): Fixed: [Basic Styles](http://ckeditor.com/addon/basicstyles) do not work when SCAYT is enabled.
* [#125](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/125): Fixed: Inline styles are not continued when writing multiple lines of styled text with SCAYT enabled.
* [#127](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/127): Fixed: Uncaught TypeError after enabling SCAYT in the CKEditor `<div>` element.
* [#128](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/128): Fixed: Error thrown after enabling SCAYT caused by conflicts with RequireJS.
## CKEditor 4.5.11
**Security Updates:**
* [Severity: minor] Fixed the `target="_blank"` vulnerability reported by James Gaskell.
Issue summary: If a victim had access to a spoofed version of ckeditor.com via HTTP (e.g. due to DNS spoofing, using a hacked public network or mailicious hotspot), then when using a link to the ckeditor.com website it was possible for the attacker to change the current URL of the opening page, even if the opening page was protected with SSL.
An upgrade is recommended.
New Features:
* [#14747](http://dev.ckeditor.com/ticket/14747): The [Enhanced Image](http://ckeditor.com/addon/image2) caption now supports the link `target` attribute.
* [#7154](http://dev.ckeditor.com/ticket/7154): Added support for the "Display Text" field to the [Link](http://ckeditor.com/addon/link) dialog. Thanks to [Ryan Guill](https://github.com/ryanguill)!
Fixed Issues:
* [#13362](http://dev.ckeditor.com/ticket/13362): [Blink, WebKit] Fixed: Active widget element is not cached when it is losing focus and it is inside an editable element.
* [#13755](http://dev.ckeditor.com/ticket/13755): [Edge] Fixed: Pasting images does not work.
* [#13548](http://dev.ckeditor.com/ticket/13548): [IE] Fixed: Clicking the [elements path](http://ckeditor.com/addon/elementspath) disables Cut and Copy icons.
* [#13812](http://dev.ckeditor.com/ticket/13812): Fixed: When aborting file upload the placeholder for image is left.
* [#14659](http://dev.ckeditor.com/ticket/14659): [Blink] Fixed: Content scrolled to the top after closing the dialog in a [`<div>`-based editor](http://ckeditor.com/addon/divarea).
* [#14825](http://dev.ckeditor.com/ticket/14825): [Edge] Fixed: Focusing the editor causes unwanted scrolling due to dropped support for the `setActive` method.
## CKEditor 4.5.10
Fixed Issues:
* [#10750](http://dev.ckeditor.com/ticket/10750): Fixed: The editor does not escape the `font-style` family property correctly, removing quotes and whitespace from font names.
* [#14413](http://dev.ckeditor.com/ticket/14413): Fixed: The [Auto Grow](http://ckeditor.com/addon/autogrow) plugin with the [`config.autoGrow_onStartup`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-autoGrow_onStartup) option set to `true` does not work properly for an editor that is not visible.
* [#14451](http://dev.ckeditor.com/ticket/14451): Fixed: Numeric element ID not escaped properly. Thanks to [Jakub Chalupa](https://github.com/chaluja7)!
* [#14590](http://dev.ckeditor.com/ticket/14590): Fixed: Additional line break appearing after inline elements when switching modes. Thanks to [dpidcock](https://github.com/dpidcock)!
* [#14539](https://dev.ckeditor.com/ticket/14539): Fixed: JAWS reads "selected Blank" instead of "selected <widget name>" when selecting a widget.
* [#14701](http://dev.ckeditor.com/ticket/14701): Fixed: More precise labels for [Enhanced Image](http://ckeditor.com/addon/image2) and [Placeholder](http://ckeditor.com/addon/placeholder) widgets.
* [#14667](http://dev.ckeditor.com/ticket/14667): [IE] Fixed: Removing background color from selected text removes background color from the whole paragraph.
* [#14252](http://dev.ckeditor.com/ticket/14252): [IE] Fixed: Styles drop-down list does not always reflect the current style of the text line.
* [#14275](http://dev.ckeditor.com/ticket/14275): [IE9+] Fixed: `onerror` and `onload` events are not used in browsers it could have been used when loading scripts dynamically.
## CKEditor 4.5.9
Fixed Issues:
* [#10685](http://dev.ckeditor.com/ticket/10685): Fixed: Unreadable toolbar icons after updating to the new editor version. Fixed with [6876179](https://github.com/ckeditor/ckeditor-dev/commit/6876179db4ee97e786b07b8fd72e6b4120732185) in [ckeditor-dev](https://github.com/ckeditor/ckeditor-dev) and [6c9189f4](https://github.com/ckeditor/ckeditor-presets/commit/6c9189f46392d2c126854fe8889b820b8c76d291) in [ckeditor-presets](https://github.com/ckeditor/ckeditor-presets).
* [#14573](https://dev.ckeditor.com/ticket/14573): Fixed: Missing [Widget](http://ckeditor.com/addon/widget) drag handler CSS when there are multiple editor instances.
* [#14620](https://dev.ckeditor.com/ticket/14620): Fixed: Setting both the `min-height` style for the `<body>` element and the `height` style for the `<html>` element breaks the [Auto Grow](http://ckeditor.com/addon/autogrow) plugin.
* [#14538](http://dev.ckeditor.com/ticket/14538): Fixed: Keyboard focus goes into an embedded `<iframe>` element.
* [#14602](http://dev.ckeditor.com/ticket/14602): Fixed: The [`dom.element.removeAttribute()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-removeAttribute) method does not remove all attributes if no parameter is given.
* [#8679](http://dev.ckeditor.com/ticket/8679): Fixed: Better focus indication and ability to style the selected color in the [color picker dialog](http://ckeditor.com/addon/colordialog).
* [#11697](http://dev.ckeditor.com/ticket/11697): Fixed: Content is replaced ignoring the letter case setting in the [Find and Replace](http://ckeditor.com/addon/find) dialog window.
* [#13886](http://dev.ckeditor.com/ticket/13886): Fixed: Invalid handling of the [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) instance with the `styles` property by [`CKEDITOR.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter).
* [#14535](http://dev.ckeditor.com/ticket/14535): Fixed: CSS syntax corrections. Thanks to [mdjdenormandie](https://github.com/mdjdenormandie)!
## CKEditor 4.5.8
New Features:

View File

@@ -1,7 +1,7 @@
CKEditor 4
==========
Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
http://ckeditor.com - See LICENSE.md for license information.
CKEditor is a text editor to be used inside web pages. It's not a replacement

View File

@@ -13,10 +13,10 @@
* (1) http://ckeditor.com/builder
* Visit online builder to build CKEditor from scratch.
*
* (2) http://ckeditor.com/builder/a3aa36a1c49fac96ad54315f97e9e05c
* (2) http://ckeditor.com/builder/d5ff452dc2f39a022cdbb070ac7e5e57
* Visit online builder to build CKEditor, starting with the same setup as before.
*
* (3) http://ckeditor.com/builder/download/a3aa36a1c49fac96ad54315f97e9e05c
* (3) http://ckeditor.com/builder/download/d5ff452dc2f39a022cdbb070ac7e5e57
* Straight download link to the latest version of CKEditor (Optimized) with the same setup as before.
*
* NOTE:
@@ -63,11 +63,13 @@ var CKBUILDER_CONFIG = {
'entities' : 1,
'filebrowser' : 1,
'floatingspace' : 1,
'font' : 1,
'format' : 1,
'horizontalrule' : 1,
'htmlwriter' : 1,
'image' : 1,
'indentlist' : 1,
'justify' : 1,
'link' : 1,
'list' : 1,
'magicline' : 1,

1337
js/ckeditor/ckeditor.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -4,21 +4,26 @@
*/
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here.
// For complete reference see:
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
// The toolbar groups arrangement, optimized for two toolbar rows.
config.toolbarGroups = [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] },
{ name: 'links', groups: [ 'links' ] },
{ name: 'insert', groups: [ 'insert' ] },
{ name: 'forms', groups: [ 'forms' ] },
{ name: 'tools', groups: [ 'tools' ] },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others', groups: [ 'others' ] },
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'others' },
{ name: 'tools' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] },
{ name: 'styles', groups: [ 'styles' ] },
{ name: 'colors', groups: [ 'colors' ] },
{ name: 'about', groups: [ 'about' ] }
{ name: 'colors' },
{ name: 'styles' },
{ name: 'about' }
];
config.removeButtons = 'Subscript,Superscript,Scayt,Anchor,Source,Outdent,Indent,Blockquote,About,PasteFromWord';
@@ -26,7 +31,13 @@ CKEDITOR.editorConfig = function( config ) {
config.resize_enabled = false;
config.toolbarCanCollapse = true;
config.toolbarStartupExpanded = false;
// Set the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Simplify the dialog windows.
config.removeDialogTabs = 'image:advanced;link:advanced';
// Enable the browser spell checking
config.disableNativeSpellChecker = false;
};

View File

@@ -15,13 +15,13 @@ body
/* Remove the background color to make it transparent */
background-color: #fff;
margin: 5px;
margin: 20px;
}
.cke_editable
{
font-size: 12px;
line-height: 1.0;
font-size: 13px;
line-height: 1.6;
/* Fix for missing scrollbars with RTL texts. (#10488) */
word-wrap: break-word;
@@ -138,3 +138,76 @@ p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
/* Widget Styles */
.code-featured
{
border: 5px solid red;
}
.math-featured
{
padding: 20px;
box-shadow: 0 0 2px rgba(200, 0, 0, 1);
background-color: rgba(255, 0, 0, 0.05);
margin: 10px;
}
.image-clean
{
border: 0;
background: none;
padding: 0;
}
.image-clean > figcaption
{
font-size: .9em;
text-align: right;
}
.image-grayscale
{
background-color: white;
color: #666;
}
.image-grayscale img, img.image-grayscale
{
filter: grayscale(100%);
}
.embed-240p
{
max-width: 426px;
max-height: 240px;
margin:0 auto;
}
.embed-360p
{
max-width: 640px;
max-height: 360px;
margin:0 auto;
}
.embed-480p
{
max-width: 854px;
max-height: 480px;
margin:0 auto;
}
.embed-720p
{
max-width: 1280px;
max-height: 720px;
margin:0 auto;
}
.embed-1080p
{
max-width: 1920px;
max-height: 1080px;
margin:0 auto;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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