Compare commits

..

1423 Commits

Author SHA1 Message Date
Romain Quetiez
9f50a2714a Releasing 2.3.1
SVN:2.3.1[4305]
2016-07-08 12:38:54 +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
Denis Flaven
6682eafb4d Oops, missing on Czech translation file.
SVN:trunk[4297]
2016-07-07 09:48:13 +00:00
Denis Flaven
b64c79d34e Force the filename (with a .csv extension) when downloading the audit errors as a CSV file.
SVN:trunk[4294]
2016-07-06 10:34:07 +00:00
Denis Flaven
e553c0bbe1 Prevent timeouts during the (lengthy) PDF conversion...
SVN:trunk[4293]
2016-07-06 09:43:20 +00:00
Denis Flaven
18f15ba9cc Fixed the case for the Emogrifier includes.
SVN:trunk[4292]
2016-07-06 09:22:43 +00:00
Guillaume Lajarige
f4e45b6c8d Updated licenses with Font Awesome
SVN:trunk[4291]
2016-07-05 14:53:04 +00:00
Guillaume Lajarige
1013cbc22f Customers portal : Added generated css files to the SVN
SVN:trunk[4290]
2016-07-05 14:47:45 +00:00
Romain Quetiez
12c64bd6e5 Internal: enable/disable data localization
SVN:trunk[4289]
2016-07-05 14:44:08 +00:00
Denis Flaven
c209b75f6b Limit the height of the "licenses" in the about box.
SVN:trunk[4288]
2016-07-05 13:38:15 +00:00
Denis Flaven
775ed7d437 Regresssion of iTop 2.3.0 beta: properly load the metamodel from the environment.
SVN:trunk[4287]
2016-07-05 13:37:40 +00:00
Romain Quetiez
dee3d55af2 Code cleanup: removed unused parameter (not used, thus confusing)
SVN:trunk[4286]
2016-07-05 12:08:00 +00:00
Denis Flaven
0d48e40c18 Regresssion: properly compute & record the history of StopWatch's sub-items.
SVN:trunk[4285]
2016-07-05 12:07:20 +00:00
Denis Flaven
a0965c2a52 Use the configurable date & time format for displaying the history of StopWatches.
SVN:trunk[4284]
2016-07-05 11:57:16 +00:00
Romain Quetiez
daccb122ae Getting ready for the release of iTop 2.3.0
SVN:trunk[4283]
2016-07-05 10:23:56 +00:00
Romain Quetiez
0a8532e27a Getting ready for the release of iTop 2.3.0
SVN:trunk[4282]
2016-07-05 10:12:36 +00:00
Denis Flaven
e7adf6559f Updated Czech translation, thanks to Lukáš Dvořák !
SVN:trunk[4281]
2016-07-05 10:10:23 +00:00
Romain Quetiez
07db5855a2 Fixed regression in 2.3.0-beta: placeholder $public_log$ was generating a mix of plain text and HTML markup whereas only plain text is expected
SVN:trunk[4280]
2016-07-05 09:50:10 +00:00
Denis Flaven
db47b2d05c Collapse the search form at the top of the main page when displaying a list of objects (drill down from a chart...), except when the page is the result of filling this form and pressing search.
SVN:trunk[4279]
2016-07-05 09:32:56 +00:00
Denis Flaven
a2eab87b7b Properly handle the creation of objects which go outside of the silo.
SVN:trunk[4278]
2016-07-05 09:19:17 +00:00
Romain Quetiez
396c4564b4 HTML formatting: TWO fixes in one! Fixed a bug introduced in 2.3.0-beta: the stylesheet cannot be defined within the email templates (aka ActionEmail) anymore. Instead, a default (ready for use) stylesheet is provided into /css/email.css and it can be overriden by the configuration parameter email_css. The fix consists in transforming the stylesheet into inline style... which fixes a limitation of gmail and Outlook that support only the inline styles. The implementation relies on a new library: emogrifier. This library has been changed (home-made utility) to be compatible with PHP 5.3 (declaration of arrays).
SVN:trunk[4277]
2016-07-04 15:06:28 +00:00
Denis Flaven
8582f6da70 Enhancement: provide some feedback to the end-user in case of a fatal error during an interactive export.
SVN:trunk[4276]
2016-07-04 14:22:24 +00:00
Guillaume Lajarige
fc7a10ff03 Synchro : Change description attribute from AttributeString to AttributeText
SVN:trunk[4275]
2016-07-01 13:47:59 +00:00
Guillaume Lajarige
7330154dd0 Customers portal : Fixed css glitch on portal instances menu
SVN:trunk[4274]
2016-07-01 10:48:02 +00:00
Romain Quetiez
beb53fd9dc When iTop is in restricted access mode (access_mode=2), the upgrade is not completely performed (profiles not updated correctly)
SVN:trunk[4273]
2016-07-01 10:36:26 +00:00
Guillaume Lajarige
9dbad63d6f Synchro : Formated last synchro date in the tooltip when viewing an Object
SVN:trunk[4272]
2016-07-01 10:14:26 +00:00
Guillaume Lajarige
21e5ca484c Export : Formatting dates from subitems in CSV, Excel, PDF exports
SVN:trunk[4271]
2016-07-01 09:45:58 +00:00
Guillaume Lajarige
41bee2b9b2 Customers portal : Home page template could not be override since template refactoring
SVN:trunk[4270]
2016-07-01 06:43:14 +00:00
Guillaume Lajarige
41556ba00b Customers portal : Final touches on portal configuration
SVN:trunk[4269]
2016-06-30 12:07:17 +00:00
Romain Quetiez
33b46483a9 Placeholders in notification: fixed the error message when the given placeholder is invalid
SVN:trunk[4268]
2016-06-29 15:03:56 +00:00
Guillaume Lajarige
970f75d5e2 PDF export : Exporting objects with AttributeImage value to default was crashing due to a "division by zero". Fixed.
SVN:trunk[4267]
2016-06-29 14:05:24 +00:00
Romain Quetiez
3a25916f00 Data synchro: web service synchro_import - usage to expose the real default date format (mySQL datetime format)
SVN:trunk[4266]
2016-06-29 13:47:58 +00:00
Guillaume Lajarige
c44284dc3c Customers portal : Fixed a bug in UserRequest edition form that prevented user to submit. Validation method was returning false on reoslution_code. Fix was to not check validators on empty && none mandatory fields (on both client and server sides).
SVN:trunk[4265]
2016-06-29 08:58:19 +00:00
Guillaume Lajarige
0ac9e21e5e Customers portal : Fixed dynamic tiles on home page APIs
SVN:trunk[4264]
2016-06-28 11:05:29 +00:00
Guillaume Lajarige
17abb729e1 Customers portal : Fixed a bug in BrowseBrick APIs. Calls to static functions were made as $this->
SVN:trunk[4263]
2016-06-28 09:10:10 +00:00
Guillaume Lajarige
00d131e3fc Customers portal : Improved error message on autocomplete field when the portal configuration is incorrect
SVN:trunk[4262]
2016-06-28 08:22:10 +00:00
Romain Quetiez
9d05c1c79c Code freeze for 2.3.0
SVN:trunk[4261]
2016-06-24 10:11:54 +00:00
Guillaume Lajarige
71f3313070 Customers portal : Comment for context tag
SVN:trunk[4260]
2016-06-23 15:03:18 +00:00
Denis Flaven
18de167d5e Internal: context tags to (programmatically) identify the context of the execution.
SVN:trunk[4259]
2016-06-23 14:57:37 +00:00
Guillaume Lajarige
c177264113 Customers portal : Final touches on default portal configuration
SVN:trunk[4258]
2016-06-23 13:59:20 +00:00
Guillaume Lajarige
31cafcf2dd Customers portal : BrowseBrick : Extra field columns can be hidden in list mode while remaining filterable. (Use case example : Hide a "keywords" attribute to enable filtering on it)
SVN:trunk[4257]
2016-06-23 12:58:34 +00:00
Denis Flaven
1e6ab3bdf0 Support of Custom Fields in the Excel export... not really nice but should be usable.
SVN:trunk[4256]
2016-06-23 09:26:24 +00:00
Denis Flaven
0ab344edee Internal: Read-only fields are no longer stored in the form as hidden fields.
SVN:trunk[4255]
2016-06-23 09:25:34 +00:00
Guillaume Lajarige
f82b5833aa Customers portal : Fixed a bug on profile picture edition. Button was not working on Firefox.
SVN:trunk[4254]
2016-06-23 08:59:18 +00:00
Guillaume Lajarige
daea9f0925 DataModel : Reserved location_id field OQL filter in order to start the JOINs from the Location object. Otherwise the Intersect limitation was raising.
SVN:trunk[4253]
2016-06-23 08:47:11 +00:00
Romain Quetiez
ebd0ae85a4 Code refactoring : fix of #876 implemented in 2.0.3 as [r3161], moved to a place where it will fix other implementations of the setup
SVN:trunk[4252]
2016-06-23 08:14:43 +00:00
Guillaume Lajarige
3b1886a435 Customer portal : Fixed a regression in CSS, form field labels were no longer bold
SVN:trunk[4251]
2016-06-23 08:00:08 +00:00
Guillaume Lajarige
eabc2a4eab Customer portal : Readonly attachments when object is not longer editable
SVN:trunk[4250]
2016-06-23 07:54:00 +00:00
Guillaume Lajarige
29cd969d49 Customer portal : Security exception raised when adding contact on a new UserRequest / Incident. This was caused by a bug in the SecurityHelper while checking scopes for a class that had no objects yet. (R-011452)
SVN:trunk[4249]
2016-06-23 07:23:56 +00:00
Romain Quetiez
31a375f640 Custom fields: solidified the internal APIs against creative usages (null =>default value)
SVN:trunk[4248]
2016-06-22 14:12:53 +00:00
Denis Flaven
218a2e2f01 Security: prevent grouping on password fields since it may lead to disclosure of the encrypted version of the password.
SVN:trunk[4244]
2016-06-22 13:50:19 +00:00
Romain Quetiez
94295f11da Fixed a regression introduced in iTop 2.3.0-beta: menu collapse mechanism broken when adding a shortcut (but it is restored when reloading the whole page).
SVN:trunk[4243]
2016-06-22 13:46:40 +00:00
Denis Flaven
d7b58a7730 Properly sanitize the "switch_env" parameter and take it into account only if it contains a valid value.
SVN:trunk[4238]
2016-06-22 12:08:58 +00:00
Denis Flaven
04133a8853 #1167 Error while upgrading db model from v 2.1 to 2.2 with orphan attachments.
SVN:trunk[4237]
2016-06-22 12:03:36 +00:00
Denis Flaven
619fe22a15 File or image upload is not supported (and thus disabled) when using the [+] button to create a new object inside a popup dialog.
SVN:trunk[4236]
2016-06-22 12:01:23 +00:00
Guillaume Lajarige
8071962bf9 Customer portal : LinkedSet fields can now be display a specific list of attributes instead of the z-list 'list' attributes. This is defined in module_design/classes/class/lists/list (see example in itop-tickets/datamodel.itop-tickets.xml)
SVN:trunk[4235]
2016-06-22 08:56:47 +00:00
Romain Quetiez
9b87d15f9a User/status must be read-only in demo mode
SVN:trunk[4234]
2016-06-21 14:50:28 +00:00
Romain Quetiez
3c9072bb65 OQL: fixed an old limitation, hierarchies can now be expressed both ways. Example of a query that now works fine: SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id WHERE child.name LIKE 'Combodo'. In the previous implementation, the operator was interpreted as '='.
SVN:trunk[4233]
2016-06-21 14:38:08 +00:00
Denis Flaven
fa2c234a43 "Search Drawer" is closed by default, unless the configuration parameter "legacy_search_drawer" is set to "true".
SVN:trunk[4232]
2016-06-21 10:20:20 +00:00
Guillaume Lajarige
a0d16b868e Customer portal : Edit profile picture
SVN:trunk[4231]
2016-06-21 10:19:28 +00:00
Denis Flaven
2773419faa New field on the User class to enable/disable user accounts.
SVN:trunk[4230]
2016-06-21 09:22:14 +00:00
Romain Quetiez
f89fa885d2 A few updates of the readme (valuable for the beta!)
SVN:trunk[4229]
2016-06-20 15:29:04 +00:00
Romain Quetiez
b34ea69cb3 #1169 Broken link to iTop Wiki in itop-tickets.htm
SVN:trunk[4228]
2016-06-20 15:26:50 +00:00
Romain Quetiez
b54f78ab1a Internal: DBSearch::Intersect to throw an exception whenever any of the merged queries have a queried class that does not correspond to the first joined class. This is a limitation of the current implementation of Intersect. Allowing such use cases would require quite a rework of that API.
SVN:trunk[4227]
2016-06-20 14:05:11 +00:00
Romain Quetiez
963a09ba6f Internal: fixed regression introduced in iTop 2.3.0 beta: no error though a mandatory attribute of type HTML has been left empty
SVN:trunk[4226]
2016-06-20 10:15:12 +00:00
Denis Flaven
feebe3f9a9 Support of formatted case log edition (with inline images) in the legacy portal.
SVN:trunk[4225]
2016-06-17 09:41:59 +00:00
Denis Flaven
636cd646aa Impact analysis display: cosmetics on tooltips: widen a bit the tooltips and prevent the text from overflowing horizontally.
SVN:trunk[4224]
2016-06-17 08:52:12 +00:00
Denis Flaven
9b774d3f72 - Make sure that the CSV Parser has enough time to run on big amount of data.
- Speedup the display of the CSV Import interactive wizard by parsing only the needed lines of the CSV data (in the first steps of the wizard).

SVN:trunk[4223]
2016-06-17 08:41:20 +00:00
Romain Quetiez
12857ceba1 #1188 Advanced customizations: allow to define a new constant or a brand new class as part of a delta
SVN:trunk[4222]
2016-06-16 15:25:05 +00:00
Guillaume Lajarige
ff5a96f92d Customers portal : Added some IDs in the layout to create new hooks
SVN:trunk[4221]
2016-06-16 12:03:42 +00:00
Guillaume Lajarige
c4660f1caf Customers portal : CSS is now separated into a Bootstrap theme stylesheet and another one specific to the template. It is also based on SASS like the console CSSs
SVN:trunk[4220]
2016-06-16 09:51:18 +00:00
Denis Flaven
dc2ee3472b Enhanced display/edition of the "Reconciliation Key" column when defining the reconciliation using the attributes.
SVN:trunk[4219]
2016-06-16 09:10:21 +00:00
Denis Flaven
ce6ed190aa Automatically remove duplicated modules (by keeping only the most recent one) when loading modules, independently of the loading order.
SVN:trunk[4218]
2016-06-16 08:41:54 +00:00
Romain Quetiez
52309bb1e5 Improved images caching: since 2.3.0-beta, iTop handles inline images (case logs, ticket description) and a picture for a person (AttributeImage). This code refactoring handles a case where the browser checks for the validity of the image and the 304 response code can then be returned without checking anything because we assume that the URL of the image contains a signature of it (or the data cannot change -attachement and inline images are in this case).
SVN:trunk[4217]
2016-06-16 08:23:15 +00:00
Romain Quetiez
2e71aee720 Notice in schema.php (Admin tools / data model) when viewing the class UserRequest
SVN:trunk[4216]
2016-06-15 13:15:43 +00:00
Guillaume Lajarige
41cb94bcf0 SCSS Compilation : Disabled max_execution_time while compiling SCSS files as it can take a while (Set back to its original value afterwards).
SVN:trunk[4215]
2016-06-15 12:42:57 +00:00
Guillaume Lajarige
5b9d2182dd Customers portal : Added GetAbsoluteAppRoot() JS function to portal.
SVN:trunk[4214]
2016-06-15 12:23:38 +00:00
Guillaume Lajarige
e6fefcc361 Customers portal : Started CSS rework to simply change BS theme
SVN:trunk[4213]
2016-06-15 11:44:23 +00:00
Guillaume Lajarige
181e14f48e Customers portal : Started CSS rework to simply change BS theme
SVN:trunk[4212]
2016-06-15 11:43:18 +00:00
Denis Flaven
8446fb028d Make sure that the setup can be launched even if the 'php-zip' module is not installed.
SVN:trunk[4211]
2016-06-15 09:44:25 +00:00
Denis Flaven
d88249eabc Trying to protect the Synchro for timeouts, since the synchro may be launched from the web (as a "web service", especially by the "collectors"). To be validated further with large amounts of data.
SVN:trunk[4210]
2016-06-14 16:11:28 +00:00
Denis Flaven
304080d74d #1167 Error while upgrading db model from v 2.1 to 2.2 with orphan attachments.
SVN:trunk[4209]
2016-06-14 12:44:29 +00:00
Denis Flaven
f74afac781 #1199 Properly handle the icon of attachments without any extension.
SVN:trunk[4208]
2016-06-14 09:39:11 +00:00
Denis Flaven
066e7bedc1 #1205 Positioning of dropdown list of "Popup Menus" on Chrome (and IE 11) when the content has been scrolled
SVN:trunk[4207]
2016-06-14 09:22:32 +00:00
Denis Flaven
899ea36bcc Typo !
SVN:trunk[4206]
2016-06-14 08:36:54 +00:00
Guillaume Lajarige
8ed94c4609 Customers portal : Typo in reference module design
SVN:trunk[4205]
2016-06-13 15:48:31 +00:00
Guillaume Lajarige
5dc86ebc5d utils::GetCSSFromSASS() : Skip processing if file is already a .css
SVN:trunk[4204]
2016-06-13 13:46:30 +00:00
Denis Flaven
4f7cc48fd4 Replacing the SCSS->CSS conversion library by a newer one made by Leaf Corcoran: http://leafo.github.io/scssphp, tweaked to work on PHP 5.3
SVN:trunk[4203]
2016-06-13 12:57:17 +00:00
Guillaume Lajarige
dfa855b4f7 Customer portal : ExternalField form field was crashing due to a typo.
SVN:trunk[4202]
2016-06-13 12:52:35 +00:00
Denis Flaven
49aeeb0202 Replace the SCSS->CSS library by a more recent and powerful one
SVN:trunk[4201]
2016-06-13 12:49:57 +00:00
Guillaume Lajarige
c9a0d2bc80 Customer portal : Object search from attribute was crashing if object class had no friendlyname.
SVN:trunk[4200]
2016-06-13 12:36:12 +00:00
Guillaume Lajarige
4a63989237 Customer portal : Fixed external key validator. Could not contain '0'.
SVN:trunk[4199]
2016-06-13 12:33:00 +00:00
Guillaume Lajarige
bd4aca770e Customer portal : HTML Entities in pre-setted autocomplete fields
SVN:trunk[4198]
2016-06-13 10:03:46 +00:00
Guillaume Lajarige
a4adf0b9ba Customer portal : Small update of the BrowseBrick in order to set a default filter value
SVN:trunk[4197]
2016-06-10 17:46:52 +00:00
Romain Quetiez
e48a1b1645 #1249 Fixed regression introduced in [r3916] (iTop 2.3.0 beta): AttributeHTML not working if the sql column name differs from the attribute code
SVN:trunk[4196]
2016-06-10 14:23:31 +00:00
Romain Quetiez
09fad78952 #1223 Custom lifecycle actions: improved the reporting when an action returns false (class/function/id logged into error.log)+ the framework now considers that no return value is equivalent to 'true'
SVN:trunk[4195]
2016-06-10 13:59:56 +00:00
Romain Quetiez
2a62b43848 XML customizations: fixed regression introduced in [r4075] (2.3.0-beta), could not change the parent of a class (which should move the class into the internal hierarchy)
+ fixed two error messages

SVN:trunk[4194]
2016-06-10 12:43:53 +00:00
Romain Quetiez
e8fb1cf236 #1207 Customization: allow the implementation of interfaces by the mean of abstract classes
SVN:trunk[4193]
2016-06-09 15:24:21 +00:00
Romain Quetiez
ac2492958c #1162 Protect data/log against reading (support of apache 2.4) -requires testing
SVN:trunk[4192]
2016-06-09 15:11:16 +00:00
Romain Quetiez
ddfc20fb7d #1233 Spanish translation: InterfaCe + Solución Aplicativa
SVN:trunk[4191]
2016-06-09 14:42:31 +00:00
Romain Quetiez
3147f36d96 #1252 Setup: make the project compatible with Ansible deployment (the file "database exi.png" was in fact not used at all!)
SVN:trunk[4190]
2016-06-09 13:34:08 +00:00
Romain Quetiez
1d379245ee #1254 Setup: iTop 2.3.0 requires PHP 5.3.6 (HTML sanitizer using the API DOMDocument::saveHTML with an argument)
SVN:trunk[4189]
2016-06-09 13:22:56 +00:00
Guillaume Lajarige
cb0fa2a5c8 Customer portal : Autocomplete field was not updating dependant fields.
SVN:trunk[4188]
2016-06-08 12:25:10 +00:00
Romain Quetiez
4c59d64025 Extending action classes (notifications): objects listed twice (in the base classes and leaf classes) in the notification page (actions tab).
SVN:trunk[4187]
2016-06-08 10:21:58 +00:00
Romain Quetiez
c92b0ea8f7 Internal: Email generation - No need to force "Content-Transfer-Encoding: 8bit". The default is "quoted-printable" and works fine if the content is made of plain text. Leaving the 8bit encoding could work but in such a case, the statement should be:
$oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit', true /*canonicalize*/);... otherwise the lines get truncated at random places (CRLF is assumed while PHP EOL is made of CR only!) -This has an impact on plain text email only.

SVN:trunk[4186]
2016-06-08 10:01:17 +00:00
Guillaume Lajarige
7ebb0c40f3 Customer portal : Updated object form manager to check if an AttributeType has the necessary API to be used in a form (instead of the temporary array enumerating available types)
SVN:trunk[4184]
2016-06-03 14:51:37 +00:00
Guillaume Lajarige
b24c2b8455 Customer portal : Improved user feedback on modal loading crashs
SVN:trunk[4183]
2016-06-03 14:50:17 +00:00
Guillaume Lajarige
281925755d Customer portal : Added info/warning/error messages to the issue log along some exceptions
SVN:trunk[4182]
2016-06-03 14:34:29 +00:00
Guillaume Lajarige
f5ae5f7c42 Customer portal : Added user feedback when modal dialog loading crashes server side
SVN:trunk[4181]
2016-06-03 13:59:06 +00:00
Guillaume Lajarige
80789ccaa9 Customer portal : Formatted date in case log entries header
SVN:trunk[4180]
2016-06-02 15:45:33 +00:00
Denis Flaven
54a40c42cd Fix: properly parse dates in synchro import. Thanks to Karl aka karkoff1212 for reporting the issue.
SVN:trunk[4179]
2016-06-02 15:12:57 +00:00
Guillaume Lajarige
2c5d95a638 Customer portal : Browse Brick : Secondary actions menu was not always opening over the right element.
SVN:trunk[4178]
2016-06-02 15:06:45 +00:00
Guillaume Lajarige
10ca60893b Customer portal : Typo in module design comment
SVN:trunk[4177]
2016-06-02 14:42:06 +00:00
Guillaume Lajarige
f86c4bb8fa Customer portal : Viewing an object now displays an edit button if the user is allowed to edit the viewed object.
SVN:trunk[4176]
2016-06-02 12:36:50 +00:00
Guillaume Lajarige
3edf777aa6 Customer portal : Manage Brick : Added an optional tag <opening_mode> to define how the objects should be open (Value can be edit|view, Default is edit). Note that even if the tag is set to edit, objects that the user isn't allowed to edit will open in view mode.
SVN:trunk[4175]
2016-06-02 11:54:25 +00:00
Guillaume Lajarige
8a2fbdfd56 Customer portal : Manage Brick : Now displays object from the oql_view scope instead of the oql_edit scope. However, opening an object will be in edition mode if the user is allowed to do so, iotherwise it will open in view mode
SVN:trunk[4174]
2016-06-02 09:29:14 +00:00
Guillaume Lajarige
07056013c2 Customer portal : Displays a message when viewing an object with no attachments in read only
SVN:trunk[4173]
2016-06-02 09:15:42 +00:00
Guillaume Lajarige
ae61a1e5eb Customer portal : SecurityHelper now outputs to IssueLog on negative result when debug mode is enabled. Warning : This ca be extremely verbose ! Use debug mode smartly.
SVN:trunk[4172]
2016-06-02 08:51:27 +00:00
Guillaume Lajarige
e0909766fd Customer portal : Manage Brick : Edit action hyperlink is now setted on the first column if there is no friendlyname. Until now, if no friendlyname was available on the object class, there was no was of editing the object.
SVN:trunk[4171]
2016-06-02 08:26:06 +00:00
Guillaume Lajarige
e9dde4ee58 Customer portal : Set autocomplete to "off" on the password form in user profile brick.
SVN:trunk[4170]
2016-06-02 07:54:57 +00:00
Guillaume Lajarige
0050de641a Customer portal : Added support for some AttributeType in forms (AttributeExternalField, AttributeHierarchicalkey, AttributeSubItem, AttributeDuration)
SVN:trunk[4169]
2016-06-02 07:47:28 +00:00
Guillaume Lajarige
445349d2d9 Customer portal : Manage brick : Displaying objects from a class without friendlyname raised an exception.
SVN:trunk[4168]
2016-06-02 07:22:32 +00:00
Guillaume Lajarige
d026c86c50 Customer portal : Fixed regression introduced by r4159. Preferences form in user profile was crashing.
SVN:trunk[4166]
2016-06-01 15:51:29 +00:00
Romain Quetiez
1fb346da67 #1235 Internal: DBObject API - external fields not up to date after changing the external key (though they seem to be in sync when inspecting the internal values, Get() does not return the expected value).
SVN:trunk[4165]
2016-06-01 14:13:20 +00:00
Romain Quetiez
df466faddf Demo mode: to not allow deleting neither changing the org of persons attached to a user account (this to make sure that the portal users will still have access to the customer portal)
SVN:trunk[4164]
2016-06-01 12:46:22 +00:00
Guillaume Lajarige
43106d3a77 #1251 Disabling log notification in config causes a fatal error
SVN:trunk[4163]
2016-06-01 09:15:24 +00:00
Denis Flaven
9fc3b52b24 Fix: cannot export an object with a property named "length" !!
SVN:trunk[4161]
2016-05-30 09:25:29 +00:00
Guillaume Lajarige
026edb523c Customer portal : Added support for demo mode
SVN:trunk[4160]
2016-05-30 09:00:02 +00:00
Guillaume Lajarige
f640558349 Customer portal : Added support for demo mode
SVN:trunk[4159]
2016-05-30 08:12:17 +00:00
Guillaume Lajarige
ae52521a3f Customer portal : Added highlight classes to items in the manage brick
SVN:trunk[4158]
2016-05-27 10:32:58 +00:00
Guillaume Lajarige
13ef1a4084 Customer portal : Changed behaviour of browse brick table in list mode. Now columns hide from left to right when screen is too narrow. As the table is based on a hierarchy, left columns are redundants and hence less important then those on the right. (Had to update dataTables files to get a fix on the responsive extension)
SVN:trunk[4157]
2016-05-27 08:37:10 +00:00
Romain Quetiez
a8f67116ea Customer portal: continuation of the previous commit... hopefully the last one before releasing 2.3.0 beta!
SVN:trunk[4156]
2016-05-26 10:00:21 +00:00
Romain Quetiez
af9bd61bb7 Preparing release 2.3.0 beta
SVN:trunk[4155]
2016-05-26 09:27:35 +00:00
Guillaume Lajarige
fdb0a8116c Customer portal : Changed user profile brick icon
SVN:trunk[4154]
2016-05-26 08:58:55 +00:00
Guillaume Lajarige
ffcc6ded74 Customer portal : Translated on going requests tabs
SVN:trunk[4153]
2016-05-25 15:45:17 +00:00
Guillaume Lajarige
6152277910 Customer portal : Home tile alignment to the left
SVN:trunk[4152]
2016-05-25 14:47:19 +00:00
Denis Flaven
62b47556bc Aligning version numbers for the modules modified since the last release.
SVN:trunk[4151]
2016-05-25 14:07:45 +00:00
Romain Quetiez
c9bfeedb50 Preparing release 2.3.0 beta
SVN:trunk[4150]
2016-05-25 13:54:08 +00:00
Guillaume Lajarige
0dcff56969 Customer portal : Good Vibrations ♫♫
SVN:trunk[4149]
2016-05-25 13:44:22 +00:00
Denis Flaven
16d0df2553 Regression fix: update read-only fields which are dependent from other fields.
SVN:trunk[4148]
2016-05-25 13:34:12 +00:00
Guillaume Lajarige
564b8a9726 Customer portal : User name truncation when too long on mobile UI
SVN:trunk[4147]
2016-05-25 13:23:37 +00:00
Romain Quetiez
3830d2414c New portal: Finalized the texts in the home view (and menus)
SVN:trunk[4146]
2016-05-25 13:07:50 +00:00
Guillaume Lajarige
2e6e6d52ca Customer portal : UI improvements on navigation menu
SVN:trunk[4145]
2016-05-25 13:00:05 +00:00
Denis Flaven
22f73506a2 Support for objects to go "out of the silo" during a transition by making sure that we can reload an object we've just saved.
SVN:trunk[4144]
2016-05-25 12:32:32 +00:00
Denis Flaven
fc4c3c9bb9 Improved the "closed requests" icon at small point sizes.
SVN:trunk[4143]
2016-05-25 12:26:50 +00:00
Guillaume Lajarige
461153967a Customer portal : Fixed a bug in brick layout title
SVN:trunk[4142]
2016-05-25 12:18:35 +00:00
Guillaume Lajarige
c4e20ea0fe Customer portal : Fixed a bug in sticky buttons
SVN:trunk[4141]
2016-05-25 11:27:35 +00:00
Guillaume Lajarige
1ea66646b6 Customer portal : Allowed HTML in tile description. Also fixed tile description css to avoid text wrapping under the decoration.
SVN:trunk[4140]
2016-05-25 09:44:30 +00:00
Denis Flaven
201f5118b3 Fine tuning of the default value for display_max_width.
SVN:trunk[4139]
2016-05-25 09:29:43 +00:00
Denis Flaven
4bebcdc63a No quotes around the default date and time format!
SVN:trunk[4138]
2016-05-25 09:28:40 +00:00
Guillaume Lajarige
f6b185388a Customer portal : Added background to user card
SVN:trunk[4137]
2016-05-25 09:16:34 +00:00
Denis Flaven
b97b9bf1d5 $this->head_html(log)$ and $this->head(log)$ now support both text and HTML case logs.
SVN:trunk[4136]
2016-05-25 08:59:26 +00:00
Guillaume Lajarige
eab0320ef0 Customer portal : Creating a request from services catalog now redirects to "on going requests" page
SVN:trunk[4135]
2016-05-25 08:33:02 +00:00
Romain Quetiez
93adab0644 New portal: IIS default config not handling correctly woff/svg files
SVN:trunk[4134]
2016-05-25 07:59:00 +00:00
Guillaume Lajarige
adc70103e0 Customer portal : UI improvements
SVN:trunk[4133]
2016-05-25 07:32:23 +00:00
Romain Quetiez
cf3d0bde5b Operational status: only in search forms (+ search results for Ticket)
SVN:trunk[4132]
2016-05-25 07:29:20 +00:00
Romain Quetiez
4187b074a9 ImageAttribute: only in XML version 1.3
SVN:trunk[4131]
2016-05-24 19:55:08 +00:00
Romain Quetiez
1a5637352b New attribute 'operational_status' : finalized with three values (ongoing, resolved and closed)
SVN:trunk[4130]
2016-05-24 16:03:47 +00:00
Romain Quetiez
1e719b97d8 New attribute: ImageAttribute
SVN:trunk[4129]
2016-05-24 15:29:44 +00:00
Denis Flaven
2299d23099 Adding an extra index to speed-up data synchronization for large volumes of data.
SVN:trunk[4128]
2016-05-24 13:49:34 +00:00
Denis Flaven
a91723befe Missing dictionary entry for the PDf export options.
SVN:trunk[4127]
2016-05-24 13:43:09 +00:00
Guillaume Lajarige
e1ea466053 Customer portal : New Combodo font and stylesheet to use some pictos as characters anywhere
SVN:trunk[4126]
2016-05-24 12:34:30 +00:00
Guillaume Lajarige
2c4ccd2302 Customer portal : Changed PortalURLMaker of default portal instance so generated links now point to an edit page instead of a view page.
SVN:trunk[4125]
2016-05-24 09:53:45 +00:00
Denis Flaven
930e51f94c Font plugin for CKEditor
SVN:trunk[4124]
2016-05-24 09:38:14 +00:00
Denis Flaven
534eef1f72 Font plugin for CKEditor
SVN:trunk[4123]
2016-05-24 09:37:32 +00:00
Guillaume Lajarige
89fa0365fd Customer portal : Services catalog displayed as a list by default
SVN:trunk[4122]
2016-05-24 09:34:17 +00:00
Guillaume Lajarige
9f8ed3ada3 Customer portal : Home UI improvements, added missing iTop logo
SVN:trunk[4121]
2016-05-24 09:26:35 +00:00
Denis Flaven
047fd088cc Excel export: write empty date (and time) cells as empty strings instead of zero (0) !
SVN:trunk[4120]
2016-05-23 16:24:12 +00:00
Denis Flaven
db010e4ddc Proper validation (and reporting) about date (and time) formats.
SVN:trunk[4119]
2016-05-23 16:11:27 +00:00
Guillaume Lajarige
bc6f73b9ec Customer portal : Fixed some bugs and rectified some default configuration parameters
- Form, ExternalKey autocomplete & regular search
- Portal power user being able to see all its silo tickets
- Worked on the UI

SVN:trunk[4118]
2016-05-23 15:31:02 +00:00
Denis Flaven
7761404755 Date and time format: exports finalization.
SVN:trunk[4117]
2016-05-23 14:39:21 +00:00
Denis Flaven
7a6e47f067 Properly display the date value (and not the current date) in the export preview.
SVN:trunk[4116]
2016-05-23 13:05:10 +00:00
Denis Flaven
409ca87f8c Initialize TimePicker in one call to prevent the time part from being reset as it happens when doing the same action in two passes (calling "options" the second time) !!
SVN:trunk[4115]
2016-05-23 12:35:36 +00:00
Guillaume Lajarige
6ce0940f67 Customer portal : Ordered languages in user preferences
SVN:trunk[4114]
2016-05-23 12:16:10 +00:00
Guillaume Lajarige
127d2a3295 Customer portal : Fixed exception in ManageBrick that was looking for objects out of its scope
SVN:trunk[4113]
2016-05-23 10:16:43 +00:00
Denis Flaven
f4ff96a552 InlineImage::FixUrls must be
1) idempotent
2) aligned with the syntax used by CKEditor
to prevent creating new entries in the history when nothing was modified.

SVN:trunk[4112]
2016-05-23 10:16:31 +00:00
Denis Flaven
64b3e12258 Updated C3Js to 0.4.11 to fix an issue (on click event) on Chrome.
SVN:trunk[4111]
2016-05-23 08:13:20 +00:00
Romain Quetiez
7aa1495c4a Customer Portal: simplified the contents of the home page (still requires icons and a review of the CSS)
SVN:trunk[4110]
2016-05-20 09:46:55 +00:00
Romain Quetiez
4a1ec12cba #1213 Losing SLA data when changing any attribute of an SLA.
SVN:trunk[4109]
2016-05-20 08:01:40 +00:00
Guillaume Lajarige
f4ab48a2d4 Customer portal : Disabled autocomplete on password fields in the user profile form
SVN:trunk[4108]
2016-05-19 15:23:40 +00:00
Guillaume Lajarige
eeb0d5c98c Customer portal : Look and feel WIP
SVN:trunk[4107]
2016-05-19 15:23:23 +00:00
Romain Quetiez
1ebafb0566 XSS: Fixed a regression caused by the fix [3994]. Object hyperlinks were escaped twice causing accuented characters displayed as '&acute;'. The API DBObject::MakeHyperLink has been clarified and the original fix moved elsewhere. The XSS injection that was not handled correctly prior to [3994] was in the display of an external key in the details of an object. To reproduce easily, inject some malicious characters in the name of the organization 'Demo' and view any object owned by Demo.
SVN:trunk[4106]
2016-05-19 09:51:09 +00:00
Denis Flaven
4a81d70bf6 Suppress a warning when exporting a case log to HTML... Limitation: be aware that wiki links are not transformed to hyperlinks in this case.
SVN:trunk[4105]
2016-05-18 09:33:17 +00:00
Guillaume Lajarige
ccc0c7e7aa Customer portal : Object search dialog when adding object to a linkedset doesn't show already added elements.
SVN:trunk[4104]
2016-05-18 09:15:44 +00:00
Guillaume Lajarige
788f7bb029 Customer portal : Adding object to linkedset could have result in duplicates. Fixed
SVN:trunk[4103]
2016-05-18 09:00:54 +00:00
Romain Quetiez
70774f1923 Model factory: _delta = if_exists, not working with <class> nodes
SVN:trunk[4102]
2016-05-18 08:22:17 +00:00
Denis Flaven
c914344a32 Security: do not show actual encrypted values, display '*****' instead.
SVN:trunk[4101]
2016-05-18 08:15:14 +00:00
Denis Flaven
3b38388c73 Support "recusrive placeholders" (i.e. $this->org_id->code$) inside notifications... when using the HTML notation (i.e. -> becomes -&gt;)
SVN:trunk[4100]
2016-05-17 19:01:22 +00:00
Guillaume Lajarige
d9a5b85c67 Customer portal : Fixed regression in request template that was introduced by #4095
SVN:trunk[4099]
2016-05-17 15:43:23 +00:00
Guillaume Lajarige
1aacf1adae Customer portal : UX adjustments on LinkedSet (Collapse toggler pictos, check all/none state)
SVN:trunk[4098]
2016-05-17 14:56:25 +00:00
Guillaume Lajarige
88866ac199 Customer portal : Form, sticky buttons as pictos only in both plain page and modal layouts. Also, pictos were added to regular bottom buttons
SVN:trunk[4097]
2016-05-17 14:55:05 +00:00
Denis Flaven
316d1f9b14 Validate date/time fields using their regular expression during an import (or synchro) to avoid passing wrong formats as-is (e.g. 01/02/16 can become 01/02/0016 instead of 01/02/2016 if you use the 4 digits format for years and pass only 2 digits !)
SVN:trunk[4096]
2016-05-17 14:51:42 +00:00
Romain Quetiez
6b465688a2 Custom Fields: API to detect forms containing only hidden fields
SVN:trunk[4095]
2016-05-17 14:09:38 +00:00
Guillaume Lajarige
6e8ee09399 Customer portal : Form, sticky buttons only when creating / editing objects
SVN:trunk[4094]
2016-05-16 17:20:36 +00:00
Guillaume Lajarige
754604b009 Customer portal : Preview for attachments
SVN:trunk[4093]
2016-05-15 09:43:53 +00:00
Guillaume Lajarige
7790f770a7 Customer portal : Form - Hiding templates when there is none in order to optimize form space (Actually hiding SubForm when there is only HiddenField)
SVN:trunk[4092]
2016-05-15 08:46:24 +00:00
Denis Flaven
72b4c549c7 Fix a regression (crash) when displaying deadline attributes.
SVN:trunk[4091]
2016-05-14 17:42:02 +00:00
Denis Flaven
1515178500 Validate date/time fields using their regular expression during an import to avoid passing wrong formats as-is (e.g. 01/02/16 can become 01/02/0016 instead of 01/02/2016 if you use the 4 digits format for years and pass only 2 digits !)
SVN:trunk[4090]
2016-05-14 17:38:07 +00:00
Denis Flaven
165dbaf245 Date and time format finalization for the exports:
- properly display the date and time as expected in the preview during an interactive export
- differentiate date vs date&time formats in the Excel export
- use the custom format in the default URL provided by the query phrasebook

SVN:trunk[4089]
2016-05-14 17:35:52 +00:00
Guillaume Lajarige
3c112eb078 Customer portal : LinkedSet widget UX improvements part 2 (Collapsing widget)
SVN:trunk[4088]
2016-05-14 17:23:43 +00:00
Denis Flaven
5540fdb7db Display the dates of the synchronization according to the date/time format defined for this language.
SVN:trunk[4087]
2016-05-14 17:06:47 +00:00
Denis Flaven
6e074f5486 Regression: properly initialize mandatory date (and time) attributes when using a custom date/time format.
SVN:trunk[4086]
2016-05-14 17:04:20 +00:00
Denis Flaven
636140bfdd Display the dates of the history according to the date/time format defined for this language.
SVN:trunk[4085]
2016-05-14 17:02:26 +00:00
Guillaume Lajarige
69165396d4 Customer portal : LinkedSet widget UX improvements part 1 (Check/Uncheck all)
SVN:trunk[4084]
2016-05-13 17:47:14 +00:00
Romain Quetiez
dab860cfbd Breadcrumb: reviewed icon and label for global search and search menus just openin a search form
SVN:trunk[4083]
2016-05-13 15:27:11 +00:00
Romain Quetiez
7380f56a50 Breadcrumb: reworked the disposition (when wrapping onto a second line, the last item could be strangely placed between both lines on Chrome)
SVN:trunk[4082]
2016-05-13 15:24:17 +00:00
Guillaume Lajarige
d0d761236b Customer portal : Improvements on form sticky buttons
SVN:trunk[4081]
2016-05-13 14:03:36 +00:00
Guillaume Lajarige
760f3a788e Customer portal : Added description to bricks. Displayed only in the home tiles.
SVN:trunk[4080]
2016-05-13 13:43:10 +00:00
Romain Quetiez
73274ec461 Customer portal: the list of service subcategories of a user request must be filtered on 'service request'
SVN:trunk[4079]
2016-05-13 13:33:05 +00:00
Guillaume Lajarige
291a5847f0 Customer portal : Portal logo source order is : "/images/itop-logo.png" < "/env-xxx/branding/portal-logo.png" < "value of //properties/logo of the portal module_design"
SVN:trunk[4078]
2016-05-13 12:06:35 +00:00
Guillaume Lajarige
2720f6e54b Customer portal : Sticky form button when form is to long to be fully displayed in the screen
SVN:trunk[4077]
2016-05-13 10:31:15 +00:00
Romain Quetiez
58b571f08a Model factory: handle the flag 'if_exists' when transforming XML from version 1.3 to older versions
SVN:trunk[4076]
2016-05-12 19:13:06 +00:00
Romain Quetiez
14a2d9960f Model factory: introduced a new variation of attribute _delta: if_exists. Use this flag to ignore a branch if the corresponding node does not exist in the data model being hacked. This is to reduce the burden of developping separate modules depending on the installation options.
SVN:trunk[4075]
2016-05-12 15:57:09 +00:00
Romain Quetiez
88c46813d9 Setup: improved the module ordering algorithm. If a module has several dependencies (inclusive OR), it must be installed after each and every of its dependency that has been selected for installation.
SVN:trunk[4074]
2016-05-12 15:50:54 +00:00
Guillaume Lajarige
bcb5e4304a Customer portal : BrowseBrick : Changed style of secondaries actions
SVN:trunk[4073]
2016-05-12 13:21:30 +00:00
Guillaume Lajarige
793d4f814d Customer portal : Tooltip not closing when opening a modal on mobile devices
SVN:trunk[4072]
2016-05-12 12:01:52 +00:00
Guillaume Lajarige
1693f73742 Customer portal : Finally translated form validation messages !
SVN:trunk[4071]
2016-05-12 10:47:38 +00:00
Guillaume Lajarige
4c9edf04dd Customer portal : Support for password field in the Bootstrap renderer
SVN:trunk[4070]
2016-05-12 10:25:07 +00:00
Guillaume Lajarige
9b11b12b07 Customer portal : Form adjustments on UserProfile brick
SVN:trunk[4069]
2016-05-12 10:24:23 +00:00
Guillaume Lajarige
6297809716 Customer portal : User Profile brick that allows basic Contact informations edition, password / preferences change from the portal
SVN:trunk[4068]
2016-05-12 10:22:23 +00:00
Guillaume Lajarige
6540c547a4 Customer portal : Fixed home tiles disposition algo
SVN:trunk[4067]
2016-05-12 10:19:30 +00:00
Romain Quetiez
9d8a2cb7bb Customer Portal: refactoring for the "new ticket" buttons, depending on the installation options (in particular, a full ITIL install has now two buttons)
SVN:trunk[4066]
2016-05-11 18:37:30 +00:00
Romain Quetiez
35c0bfea1c No need for bridge modules to be listed as installed modules in the about box. Still, they are listed in the "support information".
SVN:trunk[4065]
2016-05-11 18:32:54 +00:00
Denis Flaven
748c1853ec Programmatically allow to write on any object - if needed - independently of the profiles.
SVN:trunk[4064]
2016-05-11 16:13:48 +00:00
Guillaume Lajarige
0e5c2c3e80 Portal : Changed "no item" message for BrowseBrick
SVN:trunk[4063]
2016-05-11 14:23:35 +00:00
Denis Flaven
cc0019c090 Styles fine tuning and nicer display of the main menu (no more animation on initial load).
SVN:trunk[4062]
2016-05-11 14:20:02 +00:00
Denis Flaven
e00667c2e4 HTML texts: support of float (left/right) in the inline style tags.
SVN:trunk[4061]
2016-05-11 13:43:49 +00:00
Romain Quetiez
c1a4c0185b Customer Portal: exit if 1) there is neither UR nor INC tickets installed 2) the current login has no contact associated to it
SVN:trunk[4060]
2016-05-11 13:18:01 +00:00
Denis Flaven
1c997a5973 Removed "Essential" from the logo.
SVN:trunk[4059]
2016-05-11 13:18:00 +00:00
Romain Quetiez
236de6ce34 Customer Portal: renamed portal related modules (as seen in the about box)
SVN:trunk[4058]
2016-05-11 12:20:17 +00:00
Romain Quetiez
681e07ca73 Customer portal: do not install itop-portal-base if not required
SVN:trunk[4057]
2016-05-11 12:14:51 +00:00
Denis Flaven
e388e4b163 Bug fix: the result of CheckToWrite() was not taken into account (action failed silently) when creating an object using the [+] button inside a form.
SVN:trunk[4056]
2016-05-11 12:09:45 +00:00
Romain Quetiez
1018dc6e74 Customer portal: adjusted the versions of the recently updated module (inc. the XML format version raised to 1.3)
SVN:trunk[4055]
2016-05-11 12:05:03 +00:00
Romain Quetiez
06075805e0 Fixed regression introduced in [4022]: about box not displayed
SVN:trunk[4054]
2016-05-11 11:46:28 +00:00
Denis Flaven
37a6a5183d #1214: concurrent access lock not properly released when CheckToWrite() reports an error during a transition from one state to another.
SVN:trunk[4053]
2016-05-11 11:44:26 +00:00
Denis Flaven
ebd89194ee New flag to open/collapse the search form at the top of a page in an OQLMenuNode.
SVN:trunk[4052]
2016-05-11 10:07:36 +00:00
Romain Quetiez
243dee4312 Brand new customer portal - installed only if selected by the user in the installation wizard
SVN:trunk[4051]
2016-05-11 08:25:20 +00:00
Guillaume Lajarige
d677a20c96 Portal : Datamodel modifications to ensure setup modularity
SVN:trunk[4050]
2016-05-11 07:49:54 +00:00
Denis Flaven
ccddf1d4f0 Fix for editing HTML content containing html entities: & must be encoded as &amp; as well !!
SVN:trunk[4049]
2016-05-10 19:26:02 +00:00
Denis Flaven
1d3ab23699 Typo!
SVN:trunk[4048]
2016-05-10 17:21:44 +00:00
Romain Quetiez
0182822f76 Customer Portal: proposed by default for installation
SVN:trunk[4047]
2016-05-10 15:47:10 +00:00
Romain Quetiez
c911ce38a6 User request (all-in-one): the end-user can leave the request type undefined, in such a case, she can select any type of services and the request type gets computed when the requests is written to the DB. Still, this is possible to select a request type and the list of services is filled with the corresponding services. This behavior was necessary for the new user portal to work fine.
SVN:trunk[4046]
2016-05-10 15:38:10 +00:00
Denis Flaven
89328e1662 Prevent infinite cross-ticket recursion when propagating parent->child resolution in tickets.
SVN:trunk[4045]
2016-05-10 15:33:48 +00:00
Romain Quetiez
a618f2804b Brand new customer portal - alpha version: requires adjustments to work with various ticketing installation options
SVN:trunk[4044]
2016-05-10 15:04:44 +00:00
Denis Flaven
242f7785e6 Add the "filter" attribute into the details form of the TriggerOnThresholdReached class.
SVN:trunk[4043]
2016-05-10 14:00:34 +00:00
Denis Flaven
3335d0a453 Throw an expection in case of unexpected value for the _delta attribute in the XML...
SVN:trunk[4042]
2016-05-10 13:49:35 +00:00
Denis Flaven
1621f2ba31 Make the login page more mobile friendly.
SVN:trunk[4041]
2016-05-10 13:33:45 +00:00
Romain Quetiez
45ddc7f71b Internal: when uploading documents, get the mimetype from the file itself (if feasible) rather than relying on the mimetype of the HTTP header. This was already implemented but it was buggy and fell anytime into the fallback method.
SVN:trunk[4040]
2016-05-10 10:57:25 +00:00
Romain Quetiez
ae22bbbc81 Internal: added DBObject::RegisterURLMakerClass, to allow for overriding the standard behavior of template placeholders such as $this->org_id->hyperlink(portal)$
SVN:trunk[4039]
2016-05-09 16:01:56 +00:00
Denis Flaven
3e1607047e CKEditor's full screen mode is not supported on iOS (cf https://dev.ckeditor.com/ticket/8307)
SVN:trunk[4038]
2016-05-09 15:36:26 +00:00
Denis Flaven
da69985970 Preparing for 2.3.0 beta.
SVN:trunk[4037]
2016-05-04 12:29:33 +00:00
Romain Quetiez
6999458de6 Added credits for CKEditor (LGPL)
SVN:trunk[4036]
2016-05-04 12:23:29 +00:00
Denis Flaven
37a8db125a Portal users must now be able to add/remove links to Persons and CIs.
SVN:trunk[4035]
2016-05-04 12:17:48 +00:00
Denis Flaven
3d74c1ccaa More sample data: adding 1 Service Family for all IT services.
SVN:trunk[4034]
2016-05-04 12:16:12 +00:00
Romain Quetiez
6fae298c0c #185 Navigation Breadcrumb - Missing standard icon for classes not having a specific icon
SVN:trunk[4033]
2016-05-04 11:29:17 +00:00
Denis Flaven
e85c6ca0c5 Fix full screen button in CKEditor.
SVN:trunk[4032]
2016-05-04 10:35:12 +00:00
Guillaume Lajarige
aa788a7aad Portal : Finished integration of Date & DateTime attributes in forms
SVN:trunk[4031]
2016-05-04 10:04:06 +00:00
Denis Flaven
3c4845cf99 #1215: URL fields can now store up to 2048 characters
SVN:trunk[4030]
2016-05-04 09:55:24 +00:00
Guillaume Lajarige
2b12a86fa8 Portal : Finished integration of Date & DateTime attributes in forms
SVN:trunk[4029]
2016-05-04 09:42:14 +00:00
Denis Flaven
dc5040c1d2 Wiki syntax is supported in formatted (HTML) text fields as well as plain text areas.
SVN:trunk[4028]
2016-05-04 08:53:47 +00:00
Denis Flaven
b02e163ecc CKEditor integration fine tuning with a new "Maximize" button in the collapsed toolbar.
SVN:trunk[4027]
2016-05-04 08:26:14 +00:00
Romain Quetiez
0e25c9a7a1 #185 Navigation Breadcrumb - Do not generate new entries for "Preferences..." when the user is tuning the language
SVN:trunk[4026]
2016-05-04 07:51:06 +00:00
Guillaume Lajarige
79f73256d7 Support for Date and DateTime in portal
Fixed form validation on portal

SVN:trunk[4025]
2016-05-03 16:08:09 +00:00
Romain Quetiez
2513f0489c #185 Navigation Breadcrumb - Identify iTop by the Database and URL (to avoid messing up breadcrumbs when navigating between several instances of iTop - still buggy in case of reinstall)
SVN:trunk[4024]
2016-05-03 15:26:05 +00:00
Denis Flaven
3579f557d1 Support of date and time custom formats... for custom fields !!
SVN:trunk[4023]
2016-05-03 15:17:46 +00:00
Romain Quetiez
668e822fc6 #185 Navigation Breadcrumb - Beta version
- Any page has a breadcrumb (except if POST and a number of pages like "new object")
- Added Home + Menu buttons showed when the left pane is closed
- Configuration: breadcrumb.max_count (0 to disable)


SVN:trunk[4022]
2016-05-03 15:06:14 +00:00
Guillaume Lajarige
dd41dc05f5 Refactoring for AttributeDateTime in the portal
SVN:trunk[4021]
2016-05-03 14:44:12 +00:00
Guillaume Lajarige
f247b89342 Refactoring for AttributeDateTime in the portal
SVN:trunk[4020]
2016-05-03 14:40:56 +00:00
Denis Flaven
5386662146 Support of date and time custom formats... continuing towards the beta !
SVN:trunk[4019]
2016-05-03 09:56:02 +00:00
Romain Quetiez
7d4e9ce069 Refactoring: new API utils::GetCSSFromSASS
SVN:trunk[4018]
2016-04-29 08:04:44 +00:00
Denis Flaven
9fd07125e2 Helper class for date & time format conversions between the various conventions for expressing date & time formats.
SVN:trunk[4017]
2016-04-29 07:53:45 +00:00
Romain Quetiez
5d5b61d956 Wiki syntax: allow white spaces in the specification of a link to an object (form: [[<class>:<friendlyname>]])
SVN:trunk[4016]
2016-04-28 11:48:03 +00:00
Guillaume Lajarige
4d91e92344 Portal :
- Support for attachments in forms
- Added a loader on LinkedSet fields while form is retrieving information on server when adding objects

SVN:trunk[4014]
2016-04-28 08:22:46 +00:00
Guillaume Lajarige
75b32f2552 Attachments : Delete button's label of an attachment was hard-coded. Putted dictionnary entry instead.
SVN:trunk[4013]
2016-04-28 08:19:42 +00:00
Denis Flaven
8eba9ae714 Enhancement: Date and time formats are now configurable in iTop !! (beta version, beware!)
SVN:trunk[4011]
2016-04-22 09:26:27 +00:00
Romain Quetiez
b318d27b19 #1209 Setup or Backup failing with french error message 'Effacement du fichier ...' Regression introduced in [r3868]. Occurs when a backup fails and prevents users from seeing the mysql error report.
SVN:trunk[4010]
2016-04-22 09:26:16 +00:00
Guillaume Lajarige
90cdd28bc8 Portal : Slightly changed the GoHome() function regexp to preserve additional parameters
SVN:trunk[4009]
2016-04-21 13:19:19 +00:00
Denis Flaven
e51a6f8ff2 Bug fix: when a date/time format is specified, don't try to process columns named 'id' since obviously these are neither date/times nor a genuine attribute code.
SVN:trunk[4008]
2016-04-20 12:20:18 +00:00
Guillaume Lajarige
585a73e641 Fixed a typo in German translation files ("Deails für Benutzeranfrage" => "Details für Benutzeranfrage")
SVN:trunk[4007]
2016-04-19 14:41:59 +00:00
Romain Quetiez
2a835e5be4 Internal: query arguments could be array values, making it easier to build dynamic IN() clauses
SVN:trunk[4006]
2016-04-19 13:59:43 +00:00
Romain Quetiez
0386c53a6a #185 Navigation Breadcrumb - Fixed a regression introduced in [r4000]: default menu not displayed afer login
SVN:trunk[4005]
2016-04-19 13:55:12 +00:00
Guillaume Lajarige
e262dbfcf2 Portal : Fixed a bug in linkedset when removing last object
SVN:trunk[4004]
2016-04-19 13:47:24 +00:00
Guillaume Lajarige
8834e1b49c - Added support for ExternalKey, LinkedSet, linkedSetIndirect, CaseLog to the new portal
- Fixed some bugs on the customfields integration with he portal

SVN:trunk[4003]
2016-04-18 15:07:58 +00:00
Denis Flaven
c9c6b2f7d5 Replacing OpenFlashCharts by d3js and c3js: Farewell Flash ! (still an alpha version !)
SVN:trunk[4002]
2016-04-18 14:59:56 +00:00
Denis Flaven
7abb048b7c Replacing OpenFlashCharts by d3js and c3js: Farewell Flash ! (still an alpha version !)
SVN:trunk[4001]
2016-04-18 14:56:02 +00:00
Romain Quetiez
e27d61a525 #185 Navigation Breadcrumb - A beta version, based on the navigation history. Comments welcome!
SVN:trunk[4000]
2016-04-18 14:48:43 +00:00
Romain Quetiez
f436cece4a OQL arguments: when the value of a query argument is null, it must be considered as being a valid argument (was reported as missing). Improved the error reporting when the argument is in the form :this->attcode and the attcode is not valid for the class of 'this'.
SVN:trunk[3999]
2016-04-15 15:07:35 +00:00
Romain Quetiez
e7eb1ec7e3 CustomFields: simplified the wizard helper to cope with the edition in read-only mode (no need for the wizard helper to send the read-only/hidden values !)
SVN:trunk[3998]
2016-04-15 14:15:07 +00:00
Romain Quetiez
21564ff340 CustomFields: overload AttributeDefinition::Fingerprint
SVN:trunk[3997]
2016-04-15 09:05:39 +00:00
Denis Flaven
c32ef34307 Additional Dict entries for the bulk export parameters: keeping/removing HTML markup when exporting.
SVN:trunk[3996]
2016-04-15 08:51:52 +00:00
Romain Quetiez
2d05b110b8 Cosmetic: improved the feedback when an attribute edition control is being refreshed in the console
SVN:trunk[3995]
2016-04-11 19:02:54 +00:00
Romain Quetiez
25287a8c04 XSS: Correctly escape the name of an object when it is displayed within an hyperlink
SVN:trunk[3994]
2016-04-11 11:51:59 +00:00
Romain Quetiez
e877ec431f HTML to Text conversion not working if mb_string not present (verb mb_split)
SVN:trunk[3993]
2016-04-08 12:02:29 +00:00
Romain Quetiez
272051ea99 Internal: added verb ormCaseLog::GetAsArray()
SVN:trunk[3992]
2016-04-08 10:59:01 +00:00
Romain Quetiez
725c7d45d1 Internal: Implemented DBObject::ExecActions, enables scripting object preset/modifications
SVN:trunk[3991]
2016-04-08 07:34:38 +00:00
Denis Flaven
b991f0a6c6 Fix for a crash in the setup (regression) introduced by [r3978] (optimization of the load of dictionaries)
SVN:trunk[3990]
2016-04-07 16:11:10 +00:00
Denis Flaven
ed035b3699 YOU MUST RUN THE SETUP AFTER PERFORMING THIS UPDATE !!
- Better handling of  'auto_select' modules
- New way of implementing the "includes" of modules, now completely out of the configuration file !

SVN:trunk[3989]
2016-04-07 16:00:01 +00:00
Romain Quetiez
e9f57fd9e2 CustomFields: support of DurationField (started devs for DateField and DateTimeField)
SVN:trunk[3988]
2016-04-06 10:25:11 +00:00
Romain Quetiez
fd124ef53b CustomFields: support of RadioField or SelectObject +"radio" control
SVN:trunk[3987]
2016-04-06 09:21:01 +00:00
Romain Quetiez
0259071bdd Internal: fixed typo in utils::TextToHtml()
SVN:trunk[3986]
2016-04-06 09:17:10 +00:00
Denis Flaven
32ce26aa7d Fix for potential XSS vulnerability on uploaded file names. To be further tested before retrofitting in branches.
SVN:trunk[3985]
2016-04-05 16:15:29 +00:00
Romain Quetiez
3997ea3a23 CustomFields: support of TextAreaField
SVN:trunk[3984]
2016-04-05 13:12:25 +00:00
Romain Quetiez
f41c4f80f8 CustomFields: implemented the autocomplete behavior for SelectObjectField
SVN:trunk[3983]
2016-04-05 09:41:26 +00:00
Romain Quetiez
3eacf2e7fa Internal: typo in the reporting of page spurious chars
SVN:trunk[3982]
2016-04-05 08:52:57 +00:00
Romain Quetiez
c3f804bb29 CustomFields: fixed typos preventing fields from adding custom javascript/css files to the page
SVN:trunk[3981]
2016-04-05 08:46:51 +00:00
Guillaume Lajarige
1784653678 Cleanup and optimization of the handling/loading of the dictionary files.
SVN:trunk[3980]
2016-04-04 13:56:36 +00:00
Denis Flaven
447fc85867 Optimization: load "pdftage" (and thus tcpdf) only when needed.
SVN:trunk[3979]
2016-04-04 13:44:59 +00:00
Denis Flaven
f3773f6047 Cleanup and optimization of the handling/loading of the dictionary files.
SVN:trunk[3978]
2016-04-04 13:44:15 +00:00
Guillaume Lajarige
b741532231 New API for SelectObjectField, replaced OQL query by DBSearch
SVN:trunk[3977]
2016-04-04 10:22:57 +00:00
Romain Quetiez
f01bd61692 CustomFields: suppressed a warning when editing an object with a custom field being read-only
SVN:trunk[3976]
2016-04-04 10:00:54 +00:00
Romain Quetiez
a5d3208599 CustomFields: Attributes of type CustomFields must be removed when converting from 1.3 to 1.2
SVN:trunk[3975]
2016-03-31 15:00:02 +00:00
Guillaume Lajarige
f3cc54fe8d Updated licences file with Bootstrap and DataTables
SVN:trunk[3974]
2016-03-31 13:06:46 +00:00
Romain Quetiez
70e0fab267 Fixed regression introduced with [3912] and partially fixed in [3954] : when the autocomplete is active, then the search dialog was not working anymore.
SVN:trunk[3972]
2016-03-30 12:11:57 +00:00
Romain Quetiez
7868c4364c Label of the final class attribute could only be defined on the root class (overriding it in derived classes had no effect)
SVN:trunk[3971]
2016-03-29 14:22:13 +00:00
Guillaume Lajarige
2a5ca467fd Alpha 2.3.0 fixes :
- Multiple request templates on portal
- SelectField interface stabilization
- UI fixes on portal
- Forms updates on lifecycle

SVN:trunk[3970]
2016-03-29 12:33:08 +00:00
Romain Quetiez
2ab12d9d11 #1221 Exclude git folder from the copied folders, during the compilation process
SVN:trunk[3969]
2016-03-25 15:22:52 +00:00
Romain Quetiez
0104c3fe41 ResetStopWatch could not be used as a lifecycle action: the symptom is "The action has failed".
SVN:trunk[3967]
2016-03-25 10:02:29 +00:00
Romain Quetiez
847c1d2736 Custom fields: track the changes and improve the robustness with regards to the Exception thrown by the handler. Also fixed an issue with DBObject, causing the custom fields to be written several times if invoking DBUpdate more than once. Theoretically, this issue affects any type of attribute.
SVN:trunk[3966]
2016-03-24 10:49:04 +00:00
Romain Quetiez
922354320b Code refactoring: removed a debug trace
SVN:trunk[3965]
2016-03-24 10:40:54 +00:00
Romain Quetiez
462af27157 Custom fields: comparing two sets of values is delegated to the custom fields handler because the values must be interpreted before concluding (blind comparison resulted in objects being written though the values were equivalent)
SVN:trunk[3964]
2016-03-22 16:55:51 +00:00
Romain Quetiez
ea31d71d16 Custom fields: check data against the form prior to recording (do not rely solely on the HTML form)
SVN:trunk[3963]
2016-03-22 09:02:03 +00:00
Romain Quetiez
2150682a92 Custom fields: values not recorded if the user does not change any of the default values
SVN:trunk[3962]
2016-03-22 08:59:15 +00:00
Guillaume Lajarige
24fcb20927 Form : Started fix on CaseLog field in the portal. Only the edit value is now in the editor. Still have to display the history below.
SVN:trunk[3961]
2016-03-18 15:04:49 +00:00
Romain Quetiez
414b94405b Custom fields: better error reporting when an exception occurs while finalizing the form
SVN:trunk[3960]
2016-03-18 14:49:59 +00:00
Guillaume Lajarige
5328feb58b Form : Added LabelField class for forms.
SVN:trunk[3959]
2016-03-18 14:15:30 +00:00
Guillaume Lajarige
bc7176f07e Form : Fixed call to form_field::validate on fields with no form_field widget (typically LabelField)
SVN:trunk[3958]
2016-03-18 14:14:06 +00:00
Denis Flaven
8f4a8fc7be New icon for the new portal.
SVN:trunk[3957]
2016-03-18 11:40:38 +00:00
Romain Quetiez
81317d4df9 Custom fields: better error reporting when an exception occurs while finalizing the form
SVN:trunk[3956]
2016-03-18 10:35:09 +00:00
Guillaume Lajarige
af87ef3623 Form : Fixed dependancies check in Form::Finalize()
SVN:trunk[3955]
2016-03-17 15:25:33 +00:00
Romain Quetiez
c201ae4147 Fixed regression introduced with [3912] : autocomplete not working (new User request with lots of existing user requests)
SVN:trunk[3954]
2016-03-17 14:04:48 +00:00
Guillaume Lajarige
37e3cb6285 Form : Added some translations to the new form system
SVN:trunk[3953]
2016-03-16 16:46:58 +00:00
Guillaume Lajarige
4b7fb20eaf DBSearch : Allow join between DBUnionSearch by adding the DBUnionSearch::Join verb
SVN:trunk[3952]
2016-03-16 16:45:39 +00:00
Romain Quetiez
92d9c778e5 Prerequisites to the portal forms:
- finalize form fields in the order of their dependencies
- introduced the SelectObjectField which will implement an autocomplete (currently remains a drop-down whatever the number of items)
- code refactoring

SVN:trunk[3951]
2016-03-16 09:09:30 +00:00
Denis Flaven
1c90cd2312 Initial feedback whilie loading the 'list' tab of the impact analysis, useful when this tab is displayed first.
SVN:trunk[3949]
2016-03-15 09:39:59 +00:00
Romain Quetiez
4006fce0f2 Exclude magic parameters when listing query parameters (refactoring from run_query) This enables the use of magic parameters in the exports. The issue was less exposed in iTop 2.2.0 because only one single magic parameter was available.
SVN:trunk[3948]
2016-03-11 20:42:04 +00:00
Guillaume Lajarige
a31be78cbd CustomFields : Fixed a regression in field_set.js during validation due to touched_fields what were no longer in the form when switching templates
SVN:trunk[3947]
2016-03-11 16:03:19 +00:00
Romain Quetiez
f29af948be Custom fields: not all the values were correctly recorded (event name collision)
SVN:trunk[3946]
2016-03-11 15:05:59 +00:00
Guillaume Lajarige
44ba3d7bf8 CustomFields : Bootstrap integration
SVN:trunk[3945]
2016-03-11 14:34:16 +00:00
Guillaume Lajarige
7ea5176b56 CustomFields : Bootstrap integration
SVN:trunk[3944]
2016-03-11 12:42:21 +00:00
Romain Quetiez
e6887ab317 Custom fields: alpha version.
SVN:trunk[3943]
2016-03-10 16:55:13 +00:00
Denis Flaven
67c92ab946 Modified the "List" tab of the Impact Analysis to display only the actually impacted objects. The content of this tab is now refreshed every time the graph is rebuilt to take into account the "context" changes which causes the actual impact to change, or the filtering.
SVN:trunk[3941]
2016-03-09 18:05:14 +00:00
Guillaume Lajarige
daa090d4fe Prerequisites to the custom fields
SVN:trunk[3940]
2016-03-09 16:58:31 +00:00
Romain Quetiez
ced87e71cb Magic query arguments - fixed a regression: URL exceeding 4000 characters (!) because the serialized queries were including magic arguments. Those arguments must be computed right before executing the query. An alternative to this implementation could be to serialize a DBSearch with its parameters computed at serialization time.
SVN:trunk[3939]
2016-03-04 15:03:46 +00:00
Romain Quetiez
e26eed3142 #1210 (reopened) ...fixed a regression on commit [r3936]: dependent fields could not be loaded when there are link set attribute in the current form
SVN:trunk[3938]
2016-03-04 14:22:13 +00:00
Romain Quetiez
d33dad51f8 Prerequisites for custom fields
SVN:trunk[3937]
2016-03-02 16:16:42 +00:00
Romain Quetiez
37f6c6ed7d #1210 Dependant field not reset (servicesubcategory not reset when service is reset)
SVN:trunk[3936]
2016-03-01 14:29:35 +00:00
Denis Flaven
7e3d526de3 Background process for cleaning expired temporary attachments and inline images.
SVN:trunk[3935]
2016-02-29 17:20:43 +00:00
Denis Flaven
53029f9fc3 Optimization/bug (!): Never use the whole object as a placeholder in ApplyParams !!
SVN:trunk[3931]
2016-02-29 16:20:41 +00:00
Denis Flaven
22ccb317d6 Optimization: do not load all columns when checking if a CI is part of the "context" of a given ticket.
SVN:trunk[3929]
2016-02-29 15:47:52 +00:00
Guillaume Lajarige
ad91dc14b8 Prerequisites to the custom fields
SVN:trunk[3928]
2016-02-26 16:10:57 +00:00
Denis Flaven
6bd89f31d3 Prevent access to *any* InlineImage by just guessing its identifier, now an additional "secret" is needed, making it much harder to guess (but not 100% impossible, beware !)
SVN:trunk[3927]
2016-02-26 10:18:46 +00:00
Denis Flaven
608e94a613 Inline images in formatted case log & descriptions: beta version fixperms js The inline images are now no longer stored stored as Attachments but using a specific object InlineImage...
SVN:trunk[3926]
2016-02-25 15:06:04 +00:00
Denis Flaven
9c16b08e22 (HTML) Formatted Case Logs, Description and Notifications with inline images uploaded as Attachments. Beta Version !! - fix for missing magnificPopup()
SVN:trunk[3925]
2016-02-22 14:20:53 +00:00
Denis Flaven
63b6b95f71 Use one-way encryption for storing the token used for the "Forgotten password" feature.
SVN:trunk[3920]
2016-02-19 18:17:11 +00:00
Guillaume Lajarige
17127a5157 Prerequisites to the custom fields (and space tabs to regular tabs conversion on some files)
SVN:trunk[3919]
2016-02-19 16:43:28 +00:00
Romain Quetiez
bfadbc4098 Prerequisites for custom fields
SVN:trunk[3918]
2016-02-19 12:30:19 +00:00
Romain Quetiez
08c6bb5c5e Prerequisites for custom fields
SVN:trunk[3917]
2016-02-19 11:11:09 +00:00
Denis Flaven
4e24e9899e (HTML) Formatted Case Logs, Description and Notifications with inline images uploaded as Attachments. Beta Version !!
SVN:trunk[3916]
2016-02-19 10:03:59 +00:00
Denis Flaven
c72bdae8d7 Upgrading to CKEditor v4 !!
SVN:trunk[3915]
2016-02-19 09:32:58 +00:00
Denis Flaven
3687657dd7 Upgrading to CKEditor v4
SVN:trunk[3914]
2016-02-19 09:23:45 +00:00
Romain Quetiez
21f0adb41b Prerequisites for custom fields
SVN:trunk[3913]
2016-02-19 08:49:14 +00:00
Romain Quetiez
e0fad5e0e6 Magic query arguments:
- In addition to current_contact_id, the following arguments can be used in any OQL query (provided that the page running the query requires a  login): current_contact->attcode and current_user->attcode
- Code refactoring: magic arguments in one single place
- The "Run queries" page is now taking into account those magic arguments (do not prompt the end-user with these arguments!)

SVN:trunk[3912]
2016-02-17 18:55:46 +00:00
Guillaume Lajarige
77f8129fac Prerequisites to the custom fields
SVN:trunk[3911]
2016-02-12 13:44:10 +00:00
Guillaume Lajarige
064ae11ba8 Prerequisites to the custom fields
SVN:trunk[3910]
2016-02-11 15:25:03 +00:00
Guillaume Lajarige
d7a69118bc Prerequisites to the custom fields
SVN:trunk[3909]
2016-02-11 15:24:29 +00:00
Romain Quetiez
f2fabe4eec Prerequisites to the custom fields
SVN:trunk[3908]
2016-02-11 14:23:35 +00:00
Romain Quetiez
dc92e40429 Prerequisites to the custom fields
SVN:trunk[3907]
2016-02-11 10:55:41 +00:00
Guillaume Lajarige
9c080d51f7 Prerequisites to the custom fields
SVN:trunk[3903]
2016-02-11 10:26:06 +00:00
Denis Flaven
cf0541c93e #1202: Fix for a security vulnerability in the Configuration Editor.
SVN:trunk[3902]
2016-02-11 10:22:53 +00:00
Romain Quetiez
54be542355 Prerequisites to the custom fields
SVN:trunk[3901]
2016-02-10 15:46:05 +00:00
Romain Quetiez
225ace0d02 Preparing release 2.2.1
SVN:trunk[3898]
2016-02-03 13:16:40 +00:00
Romain Quetiez
ca24ae9632 #1196 Only administrators can add attachments by the mean of the REST/JSON API
SVN:trunk[3896]
2016-02-03 13:01:43 +00:00
Denis Flaven
9f69fd0811 #1193: When creating new object from tab with <edit_mode>add_only</edit_mode> id of the parent object was not transfered to the form. Fix provided by Vladimir Kunin. Thank you Vladimir.
SVN:trunk[3894]
2016-02-02 13:31:02 +00:00
Romain Quetiez
b978a5d219 Fixed regression introduced in [3852] : setup not working anymore ($_SESSION is unset and a notice is issued, which can prevent the install from completing, depending on your PHP error level).
SVN:trunk[3891]
2016-01-28 11:11:12 +00:00
Romain Quetiez
e7759aa79a Fixed regression introduced by [3857] : setup not working anymore (js files could not be loaded anymore)
SVN:trunk[3890]
2016-01-28 11:05:13 +00:00
Guillaume Lajarige
1f4ca07b5f Foundations for the new form system
SVN:trunk[3889]
2016-01-28 10:37:32 +00:00
Denis Flaven
3ecd768982 Enhanced statistics at the end of the setup.
SVN:trunk[3887]
2016-01-27 10:42:21 +00:00
Romain Quetiez
3cfcbeb654 Internal: fixed the verb DBObjectSearch::IsAny
SVN:trunk[3886]
2016-01-26 14:49:37 +00:00
Guillaume Lajarige
e1409ba39c Fixed a regression due to the DesignDocument factorisation :
- DesignDocument class : Namespace issue with DOMFormatException
- Compiler class : Parameters 2 & 3 of the DOMFormatException constructor needed to have a default value

SVN:trunk[3885]
2016-01-26 14:34:53 +00:00
Denis Flaven
172e255cc2 #1174: support HTML fields in the bulk modify forms (capability to enable/disable the field live)
SVN:trunk[3883]
2016-01-26 14:32:51 +00:00
Denis Flaven
ef6299c6b4 #1183: more refactoring and some robustness enhancements after tests on big datasets.
SVN:trunk[3881]
2016-01-26 13:22:47 +00:00
Denis Flaven
8a99b09e83 #1153: preserve leading zeroes (in "numeric" fields) in the Excel export.
SVN:trunk[3879]
2016-01-26 09:50:35 +00:00
Denis Flaven
9da19de860 Suppress "Notice" messages when iconv detects invalid UTF-8 characters, since it breaks the JSON output if display_errors in On...
SVN:trunk[3878]
2016-01-25 17:10:39 +00:00
Romain Quetiez
b8af72b402 Modules: added a mean to cache data that will be reset upon compilation. To be used in conjunction with ModuleDesign.
SVN:trunk[3877]
2016-01-25 16:47:05 +00:00
Denis Flaven
764c551f0f #1183: grouping threshold is now taken int account for "Depends on..." graphs (i.e. grouping backwards)
SVN:trunk[3875]
2016-01-25 14:33:00 +00:00
Denis Flaven
410c47178d #1176: empty placeholders are represented by an empty string as in previous version.
SVN:trunk[3873]
2016-01-25 12:46:56 +00:00
Denis Flaven
f53ce84f5d IconSelectorField (Design time !) can be read-only.
SVN:trunk[3871]
2016-01-21 16:02:42 +00:00
Romain Quetiez
ab0d425d93 XML: compilation error if there is no tag module_designs (completes revisions 3820 and 3861 which introduced the issue)
SVN:trunk[3870]
2016-01-21 15:36:23 +00:00
Romain Quetiez
95ca14b05c #1165 backup with errors fills up tmp-directories with lots of backup-files.
SVN:trunk[3868]
2016-01-21 14:55:31 +00:00
Denis Flaven
61e2f97d6c #1150: Spurious message "A restore is running..." - FIXED !
SVN:trunk[3864]
2016-01-20 15:56:09 +00:00
Romain Quetiez
9e6c024beb Model Factory: factorized duplicate code from ApplyChanges + fixed an issue in the error reporting
SVN:trunk[3863]
2016-01-20 13:01:58 +00:00
Guillaume Lajarige
879f5d89b9 Moved static method GetAllowedPortals() from LoginWebpage class to UserRights class.
SVN:trunk[3862]
2016-01-15 10:32:17 +00:00
Romain Quetiez
161041d379 XML: the images specified in the branding or in module_designs can be given as a fileref or a path relative to the env-production directory
SVN:trunk[3861]
2016-01-15 09:21:32 +00:00
Romain Quetiez
6d23d64e8f Code refactoring: eliminated duplicate code between MFDocument and ModuleDesign
SVN:trunk[3860]
2016-01-14 14:11:25 +00:00
Romain Quetiez
8c4e84dfaf New type of attribute: AttributeMetaEnum.
Designed to cope with the need to select tickets by operational status. The value of this attribute is computed by the framework. It depends on the actual ticket status (that attribute cannot be known by the root class because its definition varies from one type of ticket to another).
The data model has been enriched with the new attribute Ticket::operational_status. Its value is 'active' unless the ticket status is either 'rejected', 'resolved' or 'closed'. The existing dashboards have been left unchanged but should be revised to fully benefit from the new attribute (e.g. Open requests, Open problems, etc.)
Note: the alpha version of the compiler had already been committed by mistake a few days ago.

SVN:trunk[3859]
2016-01-13 14:35:21 +00:00
Romain Quetiez
706940769b Compiler: Model alterations not flattened prior to compilation (when using the setup UI) -no need for doing the job twice (compiling from within the toolkit)
SVN:trunk[3858]
2016-01-12 10:32:46 +00:00
Romain Quetiez
3fe2aa3b1d Portal: Use absolute URLs for js+css embedded into iTop (login prompt not working with the usage of symlinks or rewrite rules)
SVN:trunk[3857]
2016-01-12 09:15:37 +00:00
Guillaume Lajarige
1f4d7f2a32 Updated licenses with Silex
SVN:trunk[3856]
2016-01-11 15:34:33 +00:00
Guillaume Lajarige
57f0cce318 Added Silex framework
SVN:trunk[3855]
2016-01-11 15:32:14 +00:00
Romain Quetiez
e95d0a4722 Compiler: Model alterations not flattened prior to compilation (when using the setup UI)
SVN:trunk[3854]
2016-01-11 14:28:02 +00:00
Denis Flaven
f37030fe26 internal: new autoOpen flag.
SVN:trunk[3853]
2016-01-06 17:38:05 +00:00
Romain Quetiez
3be0bc8ca8 Improved the User Rights management API:
- new verbs: HasProfile and ListProfiles
- doing less queries (no need for listing all the profiles, caching the user profiles into the SESSION cookie
- did some code cleanup (unused variables)

SVN:trunk[3852]
2015-12-15 20:30:30 +00:00
Denis Flaven
65a7a8ee56 Properly read radio button values inside a form.
SVN:trunk[3849]
2015-12-14 13:02:08 +00:00
Romain Quetiez
ab38ce63a5 Portal: let the administrator specify an alternative URL for the portals (rewriting rules)
SVN:trunk[3848]
2015-12-10 13:06:42 +00:00
Denis Flaven
e92c6e5298 (internal) Remove _altered_in when exporting the delta.
SVN:trunk[3847]
2015-12-09 15:23:46 +00:00
Denis Flaven
76df404c8d Keep track of which module altered which node in the XML.
SVN:trunk[3845]
2015-12-09 14:56:51 +00:00
Guillaume Lajarige
d3a2841fef Error : Deleted files from a wrong commit.
SVN:trunk[3844]
2015-12-07 11:47:17 +00:00
Guillaume Lajarige
d60a4aa740 Portal : Sample to show how to alter a twig template in the portal. This sample replaces the main page layout by putting the navigation bar on the left side (except for mobile where it stays on the top).
SVN:trunk[3843]
2015-12-04 12:57:50 +00:00
Guillaume Lajarige
6538d7af1e DOMFormatException : Overloaded the constructor to add an optionnal parameter $node (\DOMNode) that will append informations about the node and the exception line number to $message.
SVN:trunk[3842]
2015-12-02 10:46:00 +00:00
Guillaume Lajarige
c69279ee20 Fixed a typo in ModuleDesignElement->Dump() function. Was creating an object of iTopDesignDocument class instead of ModuleDesign class.
SVN:trunk[3839]
2015-12-02 10:38:24 +00:00
Denis Flaven
a16e746aa1 Fixed the computation of the lowest common ancestor.
SVN:trunk[3837]
2015-12-02 10:32:37 +00:00
Romain Quetiez
b1f62c8409 Internal: dehardcoded OqlUnionQuery::GetClass against the metamodel reflection API
SVN:trunk[3836]
2015-12-01 16:23:35 +00:00
Romain Quetiez
4a85f7f12b Added AttributeDef::EnumTemplateVerbs, to generate the documentation about the available attribute formatting placeholders
SVN:trunk[3835]
2015-11-30 16:56:22 +00:00
Denis Flaven
818be68c2d Make sure we don't redefine CoreException.
SVN:trunk[3833]
2015-11-30 14:07:18 +00:00
Denis Flaven
7511391aed Added structured error reporting in case of missing dependencies for the modules to install.
SVN:trunk[3831]
2015-11-25 16:55:58 +00:00
Denis Flaven
d0a50adf32 Properly create DOMNodes with a text content (beware of XML entities inside the text)
SVN:trunk[3829]
2015-11-25 16:52:49 +00:00
Denis Flaven
c9576c696a Support validation patterns contains a forward slash
SVN:trunk[3827]
2015-11-25 16:49:20 +00:00
Guillaume Lajarige
908b442b26 Core : Added CloneWithAlias function to DBSearch class. It creates a new DBObjectSearch from a DBSearch with a new alias.
SVN:trunk[3826]
2015-11-25 11:16:30 +00:00
Denis Flaven
9687e9985e Support of derived classes in "add_remove" edition mode for AttributeLinkSet fields (the search form was not refreshing / loading properly when toggling the class to search for).
SVN:trunk[3822]
2015-11-20 14:16:07 +00:00
Romain Quetiez
07e4b18d06 #1173 Error during setup on a development system (XML containing unwanted text)
SVN:trunk[3821]
2015-11-16 15:16:28 +00:00
Romain Quetiez
93654dc656 Core: a module can have its own design defined in XML (/itop_design/modules_designs/module_design) and accessed at run time via the class ModuleDesign. Switching to XML version 1.3.
SVN:trunk[3820]
2015-11-10 12:39:45 +00:00
Denis Flaven
4be5334829 Make ReloadSearchForm work properly when the "submit" event handler is declared either with or without a "namespace" portion (e.g. 'submit.itop' vs 'submit')
SVN:trunk[3816]
2015-11-09 10:43:01 +00:00
Romain Quetiez
0f4301af01 Core API: added DBSearch:SetSelectedClasses
SVN:trunk[3815]
2015-11-09 10:35:51 +00:00
Romain Quetiez
b071c47674 PHP warning issued when the CSS is rebuilt (SASS lib)
SVN:trunk[3814]
2015-11-09 10:26:11 +00:00
Romain Quetiez
05e9f394f0 Improved the error reporting when assembling data model XML files (full path and line number of the faulty node)
SVN:trunk[3813]
2015-11-06 11:35:46 +00:00
Denis Flaven
2027becad2 #1164: typo in German localization.
SVN:trunk[3811]
2015-10-26 10:57:40 +00:00
Denis Flaven
e7170755d8 Support the download of "bigger-than-memory" backup files.
SVN:trunk[3809]
2015-10-26 10:38:57 +00:00
Romain Quetiez
d0a0d3c93c Fixed regressions due to the recent code refactoring [3803]
SVN:trunk[3807]
2015-10-12 15:35:05 +00:00
Denis Flaven
2db785477c Do NOT localize finalclass values in REST/JSON.
SVN:trunk[3805]
2015-10-12 12:39:09 +00:00
Denis Flaven
31ec3152f9 Code refactoring to make the OQL parsing self contained in the "oql" subdirectory.
SVN:trunk[3803]
2015-10-12 10:01:59 +00:00
Denis Flaven
7b8b469f5a Added a version number (arbitrary initialized to 2015-08-31 for iTop v2.2.0) to the OQL Lexer/parser.
SVN:trunk[3801]
2015-10-12 09:52:39 +00:00
Romain Quetiez
bc4737ac23 #1159 Cannot add edge (impact analysis not working)
SVN:trunk[3799]
2015-10-09 15:25:18 +00:00
Denis Flaven
6dc190d369 Do not rely on MetaModel::GetRootClass() to check the data model, use the abstraction of ModelReflection instead to keep the code portable.
SVN:trunk[3797]
2015-10-08 15:54:23 +00:00
Denis Flaven
9980d2e72a #1156: properly escape file paths containing spaces
SVN:trunk[3795]
2015-10-05 19:52:06 +00:00
Denis Flaven
2d34510f50 Better error reporting when the setup fails to create a directory.
SVN:trunk[3793]
2015-09-30 14:08:04 +00:00
Romain Quetiez
ef57f870ac Internal - MFFactory: fixed GetDelta when there is no change at all
SVN:trunk[3792]
2015-09-28 12:49:54 +00:00
Denis Flaven
7105b7a5fa Make sure that the images are reloaded when the application is upgraded.
SVN:trunk[3789]
2015-09-22 16:14:37 +00:00
Denis Flaven
1992adfac2 Make sure that the images are reloaded when the application is upgraded.
SVN:trunk[3788]
2015-09-22 15:55:22 +00:00
Denis Flaven
c2e8eca577 Datamodel version number bumped to 2.2.0
SVN:trunk[3787]
2015-09-22 15:30:01 +00:00
Denis Flaven
0cc466dd7e Make sure that the images are reloaded when the application is upgraded.
SVN:trunk[3786]
2015-09-22 15:16:12 +00:00
Denis Flaven
3eec1d358c Make sure that the images are reloaded when the application is upgraded.
SVN:trunk[3785]
2015-09-22 15:14:04 +00:00
Denis Flaven
1cc38fb58e Make sure that the stylesheets and favicons are reloaded when the application is upgraded.
SVN:trunk[3784]
2015-09-22 14:27:53 +00:00
Denis Flaven
91479bba53 New favicon for the new logo!
SVN:trunk[3783]
2015-09-22 14:26:50 +00:00
Denis Flaven
1d4a3e780d Remove the left padding and the orange arrow when printing hyperlinks.
SVN:trunk[3782]
2015-09-22 14:26:12 +00:00
Romain Quetiez
af9a419e84 Release 2.2.0. Aligning the versions of the modules that have changed since the last (beta) release.
SVN:trunk[3781]
2015-09-22 13:55:37 +00:00
Romain Quetiez
b311e924cd Releasing 2.2.0 RC
SVN:trunk[3780]
2015-09-22 13:08:13 +00:00
Denis Flaven
5c9b221b4c Properly cut long case log entries before encoding them in HTML.
SVN:trunk[3779]
2015-09-22 12:52:28 +00:00
Romain Quetiez
e94282459e Historisation of attachments: (internal) record the attachment as an external key with an automatic reset (when the attachment gets deleted)
SVN:trunk[3778]
2015-09-22 12:16:25 +00:00
Romain Quetiez
77a0c0a7c6 Historisation of attachments: added/removed attachments must be tracked within the same change as for other attributes.
SVN:trunk[3777]
2015-09-22 11:55:44 +00:00
Denis Flaven
c8fa3870db Integration of the Czech translation provided by Lukáš Dvořák. Thanks a lot Lukáš !
SVN:trunk[3776]
2015-09-18 15:05:23 +00:00
Denis Flaven
4261923126 Updated readme for the 2.2.0 version...
SVN:trunk[3775]
2015-09-18 09:23:10 +00:00
Denis Flaven
554a462809 Preserve the initial sort order on lists by determining the default sort order of the tables based on the equivalence between the "friendlyname" and another actual field of the class.
SVN:trunk[3774]
2015-09-17 17:22:07 +00:00
Denis Flaven
1206cc42bc #1151 Error (with no explanation) when deleting some 1-N links
SVN:trunk[3773]
2015-09-17 17:00:56 +00:00
Denis Flaven
48ab835646 Concurrent lock still has some minor issues, don't enable it by default.
SVN:trunk[3772]
2015-09-17 16:39:05 +00:00
Denis Flaven
bcd9141db6 #384: (continued) ActionEmail should bre read-able by everyone...
SVN:trunk[3771]
2015-09-17 15:33:41 +00:00
Denis Flaven
e1fd65fe47 Typo...
SVN:trunk[3770]
2015-09-17 14:06:16 +00:00
Erwan Taloc
b2fe3cb033 French translation for the attribute servicefamily_id in the class Service (module Service for Provider)
SVN:trunk[3769]
2015-09-17 14:01:15 +00:00
Erwan Taloc
df9cb7f0d4 French translation for the attribut parent_problem_id in the clas Incident
SVN:trunk[3768]
2015-09-17 14:00:03 +00:00
Denis Flaven
07fdeb9284 Printer-friendly version: hide the "eye" icon inside "legends" when printing.
SVN:trunk[3767]
2015-09-17 07:47:42 +00:00
Denis Flaven
6d04633daf Protects the setup against non-existing classes... to be renamed! Useful for heavily customized models where some very basic classes have been deleted.
SVN:trunk[3766]
2015-09-16 17:05:44 +00:00
Denis Flaven
2d95c131fc #384: Triggers should not be in the "bizmodel" category. User rights do not apply to such objects...
SVN:trunk[3765]
2015-09-16 15:45:10 +00:00
Denis Flaven
853c96478b #1106, #1122: Added a new option 'start_tls' (false by default) and improved debugging capabilities for troubleshooting when something goes wrong with LDAP. Thanks to Karl (karkoff1212) for the hint.
SVN:trunk[3764]
2015-09-16 15:31:22 +00:00
Denis Flaven
86a7d133f3 Make the 'curl' options overridable when calling utils::DoPostRequest()
SVN:trunk[3763]
2015-09-16 14:38:31 +00:00
Denis Flaven
1a6559efde Computation of the impacted items in two passes to properly handle the "context" queries.
SVN:trunk[3762]
2015-09-16 14:03:00 +00:00
Denis Flaven
c7cf8a9f74 Nicer icons...
SVN:trunk[3761]
2015-09-16 13:57:53 +00:00
Denis Flaven
8593f00917 Enhancement: better display of the "Attachments" (addition/removal) in the history.
SVN:trunk[3760]
2015-09-14 14:48:13 +00:00
Denis Flaven
6fd2c81315 History display enhancement: whenever a new case log entry is added, display its content in the history. The display is truncated at a configurable max length. The user can expand/collapse the truncated text, entry per entry. The text is not truncated when printing.
SVN:trunk[3759]
2015-09-14 13:46:48 +00:00
Denis Flaven
3cbb0e974e Completed unit tests to cover 1-N links and to emulate the behavior of the user interface for N-N links.
SVN:trunk[3758]
2015-09-14 12:21:34 +00:00
Denis Flaven
02aa8339f8 Cosmetics on menus, details and the top bar...
SVN:trunk[3757]
2015-09-12 18:46:39 +00:00
Denis Flaven
7f64982fc0 Cosmetics: the refresh button is now displayed as part of the "actions" at the top-right of the "details".
SVN:trunk[3756]
2015-09-12 14:38:06 +00:00
Denis Flaven
d2e78d0292 Unit tests fixes...
SVN:trunk[3755]
2015-09-12 14:34:35 +00:00
Denis Flaven
11b768dace Update the position of the dialog's buttons after adjusting the disposition of the search form.
SVN:trunk[3754]
2015-09-12 14:33:32 +00:00
Denis Flaven
972c94bff7 #1148: Fixed dashboards upload: use the more modern fileupload component, since we now hook the ajax call in iTopWebPage and removed references to the old component ajax.fileupload from (almost) everywhere...
SVN:trunk[3753]
2015-09-12 12:06:33 +00:00
Denis Flaven
489820cfe7 #1049: CSV import (and edition) of n:n links. The Differences() function is NOT commutative: the original value (i.e. the one from the database) must the the first argument.
SVN:trunk[3752]
2015-09-12 09:29:32 +00:00
Denis Flaven
a3c4454090 Usability enhancement: don't clear the "Organizations" auto complete (for the silos) without purpose when clicking on it... empty the field only when the displayed value means "All organizations".
SVN:trunk[3751]
2015-09-12 09:18:27 +00:00
Denis Flaven
bc6acee1f1 Cosmetics on the "autocomplete": more compact by default (20 chars instead of 30), and buttons evenly spaced.
SVN:trunk[3750]
2015-09-12 09:14:39 +00:00
Romain Quetiez
49a189c920 Internal: allow to stop a stop watch at a specified time (case exchange) -requires testing
SVN:trunk[3749]
2015-09-11 15:21:35 +00:00
Romain Quetiez
a35488b540 Added unit tests for the recording of linksets from one end (->Set('xxxxx_list')
SVN:trunk[3748]
2015-09-11 15:00:57 +00:00
Romain Quetiez
6b7071726b #1147 Documented the limitations for links between connectable CIs and network devices
SVN:trunk[3747]
2015-09-11 13:31:19 +00:00
Romain Quetiez
7d0282e59d #1145 nad #1146 Documented the limitations for links between connectable CIs and network devices
SVN:trunk[3746]
2015-09-11 12:39:40 +00:00
Romain Quetiez
f26bcd812c Could not add more than one link between a given server and a given network device. This is a regression in 2.2.0 beta. This issue affect N-N links where duplicates are allowed. One single link is being affected in the standard datamodel.
SVN:trunk[3745]
2015-09-11 12:03:22 +00:00
Denis Flaven
33762796b8 #1087: the sort order on "group by" dashlets inside a dashboard is now saved as a user preference.
SVN:trunk[3744]
2015-09-10 07:33:33 +00:00
Denis Flaven
38b6582080 Finishing touch to the "Printer friendly version" of the details page.
SVN:trunk[3743]
2015-09-09 14:48:14 +00:00
Romain Quetiez
cd3122d597 #1144 Audit category having no rule -> PHP notices when showing the report + improved the behavior when the OQL of a rule is wrong.
SVN:trunk[3742]
2015-09-09 13:38:52 +00:00
Romain Quetiez
b28a4c029c #1143 Records any change (add/remove/modify) for link sets that can be considered as one of the characteristics of a class (currently those having edit mode = in place)
SVN:trunk[3741]
2015-09-09 13:19:00 +00:00
Denis Flaven
85899e6ac0 Cosmetics: pixel perfect alignment of the "actions" buttons.
SVN:trunk[3740]
2015-09-09 10:33:08 +00:00
Denis Flaven
e21656c550 #1142 Dashboard editor: protects from unwanted "exit" without saving the modifications:
- mark the dashboard as modified when a dashlet was added / moved / deleted
- prevent clicking on the hyperlinks inside the preview of the dashboard

Unrelated modification of the stylesheet to make "actions" buttons look nicer (no gap in the background color) when the displayed at a zoom level different from 100% (e.g. 90% or 75 %)

SVN:trunk[3739]
2015-09-09 09:51:02 +00:00
Denis Flaven
8fec8b7f80 Usability enhancement: Autocomplete: do NOT clear the typed text when the value does not match one of the possible values, but clear the actual underlying value so that the input field gets marked as "invalid" if it is mandatory.
SVN:trunk[3737]
2015-09-09 09:38:17 +00:00
Romain Quetiez
8b45928d11 Instrumented the code to help in solving the "restore runing" issue. Completion of commits [3733] znd [3595] = issue an exception if something is going wrong within iTopMutex::TryLock (exceptions are correctly handled in the various places where TryLock is invoked)
SVN:trunk[3736]
2015-09-09 09:23:54 +00:00
Denis Flaven
98150db0b4 Protects the onwership lock from a legitimate loss of the lock. No popup when leaving for real.
SVN:trunk[3735]
2015-09-08 15:58:20 +00:00
Romain Quetiez
96a4b83e31 Internal: buggy Exception handlers for some query APIs in CMDBSource
SVN:trunk[3734]
2015-09-08 15:46:09 +00:00
Romain Quetiez
84c31da226 Instrumented the code to help in solving the "restore runing" issue. We've added traces into the error.log file:
- Log restore begin/end
 - Log if detecting that a restore is running (and displaying the banner)
 - Log any Exception occuring during the detection (instead of just ignoring it)

SVN:trunk[3733]
2015-09-08 15:42:47 +00:00
Denis Flaven
cad5e703f8 Cosmetics:
- Better use of the space in the search form: multi-select drop down list are now small when closed and larger when opened
- Nicer feedback when hiding/showing sections in the "printable version" of a details page.

SVN:trunk[3732]
2015-09-08 14:06:00 +00:00
Romain Quetiez
62959a89bc #1091 CAS memberships broken (parameter "cas_memberof" NOT given as a regular expression, bugged since iTop 2.0 or earlier)
SVN:trunk[3731]
2015-09-08 12:39:02 +00:00
Denis Flaven
c29f2eccaf Dictionary: explanation of the "Impacted CIs" tab.
SVN:trunk[3730]
2015-09-07 15:18:15 +00:00
Denis Flaven
664cfbf014 Better protection of the impact analysis against invalid configuration of the "Context".
SVN:trunk[3729]
2015-09-07 15:16:30 +00:00
Romain Quetiez
e1acce6e6e #1134 Query returning a "null row": just make sure that the row gets displayed (still surprising... see ticket #1138 to follow up on the suppression of those ghost rows)
SVN:trunk[3728]
2015-09-07 14:42:30 +00:00
Romain Quetiez
5fa83c84d3 Exports: attribute "final class" is either translated or not, depending on the no_localize option (all formats concerned with this option: CSV and spreadsheet -That field is not present in the XML format output)
SVN:trunk[3727]
2015-09-07 14:30:07 +00:00
Romain Quetiez
1bb2d168fa Spreadsheet export: fix = do take the no_localize option into account
SVN:trunk[3726]
2015-09-07 14:27:55 +00:00
Denis Flaven
52ad33f5b2 Forced the PDF produced by the impact analysis to be downloaded as an attachment, otherwise on some browsers the result cannot be saved.
SVN:trunk[3725]
2015-09-07 14:18:08 +00:00
Denis Flaven
d9adcf01cd Export bug fix: French default value for the downloaded file should not contain a comma...
SVN:trunk[3724]
2015-09-07 13:33:06 +00:00
Denis Flaven
81d19c8804 Export bug fixes:
- Properly handle on utf-8 CSV exports
- Allow non administrators to run the export in interactive mode (since it is used by the "Export..." actions)

SVN:trunk[3723]
2015-09-07 13:27:27 +00:00
Denis Flaven
5cbcebb79e Assign a meaningful name (and mime type) to the files produced by the (non-interactive) web export.
SVN:trunk[3722]
2015-09-07 10:38:03 +00:00
Romain Quetiez
40990020b1 #1140 UNION queries not working -in fact, loss of the optimization on column load when filtering on org hierarchies (retrofit possible but the fix will be located in MetaModel)
SVN:trunk[3721]
2015-09-07 10:30:58 +00:00
Romain Quetiez
5153139581 #564 Prompt for an update in a case log on a lifecycle transition. Can be retrofitted easily. Associated with commit [r3687]
SVN:trunk[3720]
2015-09-04 13:33:04 +00:00
Denis Flaven
c852cd8e09 Regression due to the fix of #1107 (revision 3647): the settings query_cache_enabled was always written as "false" in the default configuration. This is now fixed.
Warning: if you upgrade your iTop installation from the 2.2 beta, you MUST change this value back to true in the configuration file to avoid a severe slow down of the application.

SVN:trunk[3719]
2015-09-04 13:31:25 +00:00
Romain Quetiez
680104109b Typo: a pint of what? (pRint preview)
SVN:trunk[3718]
2015-09-04 13:13:33 +00:00
Romain Quetiez
5f0938d01b Fixed regression introduced in 2.2.0 beta. Warning issued when opening an organization for modification
SVN:trunk[3717]
2015-09-04 12:44:14 +00:00
Denis Flaven
1e533b24d1 Fix: Make sure that the "ownership lock" is always released when clicking on the "Cancel" button of a form.
SVN:trunk[3716]
2015-09-04 09:52:22 +00:00
Denis Flaven
7fa99cedee Impact analysis cosmetics:
- remove empty groups, since it may happen
- properly scale the borders of groups and redundancy groups
- automatically rescale the graph when showing/hiding the "Filter" tab

SVN:trunk[3715]
2015-09-04 09:22:36 +00:00
Romain Quetiez
09cbf63c5a CSV import GUI: now matching more representations (object id and external fields that are external keys)
SVN:trunk[3714]
2015-09-04 08:40:32 +00:00
Denis Flaven
be3bce26ed Impact analysis enhancement:
- Some of the "context" rules are marked as "default=yes"
- Only the "default" context rules are used for the initial display of the impact analysis graph AND are used to compute the impacted items of a ticket.

SVN:trunk[3713]
2015-09-03 16:56:44 +00:00
Romain Quetiez
5425f55af7 Exports further improved:
- Support reconciliation keys for every external key
- Better support for Case logs and multiline text fields (both in the preview and in the results)
- Do not repeat identical columns in the list of proposed columns. Examples with UserRequest: friendlyname is equivalent to ref, UserRequest::caller_name is equivalent to UserRequest::caller_id->name
- Optimized the preview for huge data sets (OptimizeColumnLoad)
- Cosmetics on the preview
- Labels for ids aligned with the labels used by the CSV import feature
- Fixed Stop Watch output for PDF/HTML/spreadsheet formats

SVN:trunk[3712]
2015-09-03 16:16:17 +00:00
Denis Flaven
b6341741c3 Refresh of the "Groups" tab in the impact analysis display, when the whole graph is refreshed.
SVN:trunk[3711]
2015-09-03 09:11:09 +00:00
Denis Flaven
a4f1a8f5ff Impact analysis improvements:
- Better layout and grouping of the graph
- Made the tooltip for groups helpful

SVN:trunk[3710]
2015-09-02 16:43:32 +00:00
Romain Quetiez
aa2ab1118a Exports: a fields spec can now be an extended attribute code (e.g. location_id->org_id->parent_id->code)
SVN:trunk[3709]
2015-09-01 15:00:55 +00:00
Romain Quetiez
71048be499 Optimization (regression introduced in 2.1.0): huge tables and HTML exports of tickets can take long to execute (require 1 query per fetched row)
SVN:trunk[3708]
2015-09-01 14:38:33 +00:00
Denis Flaven
cba724d676 Prevent timeouts during the computation of the impact analysis + keep the columns (and ordering) in the lists of objects when creating a PDF of the impact analysis.
SVN:trunk[3707]
2015-09-01 14:36:47 +00:00
Denis Flaven
6f1d186287 #1137: portal configuration was too limited. Now one "allow" profile is enough to allow access to a given portal.
SVN:trunk[3706]
2015-09-01 12:55:46 +00:00
Romain Quetiez
4674658cfa User portal as a module: the Cancel button (ticket creation wizard) was not working
SVN:trunk[3705]
2015-09-01 09:46:08 +00:00
Romain Quetiez
4cfcb60e59 REST/JSON services. Take the user rights into account. Something was already done for core/create and core/delete, but the symptoms were not clear. The other verbs (update, apply_stimulus, get and get_related) had no protection at all.
SVN:trunk[3704]
2015-08-28 10:52:59 +00:00
Romain Quetiez
a230861afa Export (all formats but XML):
- code refactoring
- suppressed the '*' for mandatory ext keys (buggy anyway)
- fixed issue with redundant columns (resulting in a badly formatted export)
- check that the current user has the rights to "bulk read" the selected objects (that depend on the selected fields)

SVN:trunk[3703]
2015-08-28 09:06:46 +00:00
Romain Quetiez
e2207dc74c Export (legacy): bulk read must be authorized for all the queried classes
SVN:trunk[3702]
2015-08-27 13:20:42 +00:00
Romain Quetiez
16b68ee154 Export: prevent from usage by a non admin (at the page level)
SVN:trunk[3701]
2015-08-27 13:18:49 +00:00
Romain Quetiez
1331f91061 #1123/#1133 The optimization on loaded columns in SQL queries was inoperant for some queries, resulting in a stopper issue if such queries were added to a union query (2.2.0 beta)
SVN:trunk[3700]
2015-08-27 07:32:41 +00:00
Romain Quetiez
138423aeec Customizations/XML: clearer error reporting when encountering a duplicate value for an AttributeEnum
SVN:trunk[3699]
2015-08-26 15:25:55 +00:00
Romain Quetiez
20815f1a91 Exports: continuation of commit 3681 (Make the correct column name for friendly names (ext key -> ext field) enlarged to ALL formats with the exception of XML
SVN:trunk[3698]
2015-08-26 13:08:02 +00:00
Romain Quetiez
35c57bb10c Export/XML: documented options (no_localize / linksets) + added external fields and friendly name for the external keys, both on the exported objects and the links (linkets=1)
SVN:trunk[3697]
2015-08-26 09:41:32 +00:00
Romain Quetiez
5fd653dae5 Export/XML: new option to include link sets (default: no)
SVN:trunk[3696]
2015-08-24 14:44:28 +00:00
Romain Quetiez
5a9b8a7bb0 Query phrases: if the attribute 'fields' is left empty, then propose the legacy export URL and keep the user informed about the limitations
SVN:trunk[3695]
2015-08-24 10:10:04 +00:00
Romain Quetiez
5277a9eb38 Exports: support multi-column queries (e.g. SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id) with null values
SVN:trunk[3694]
2015-08-24 08:01:24 +00:00
Romain Quetiez
b1887ae431 #1111 Could not attach a UserRequest to a Problem (1-N links). Could not detach either! This fix requires attention: it is assumed that an item of a link set, if it is "modified" then its key to the current object has already been set.
SVN:trunk[3693]
2015-08-21 10:27:54 +00:00
Romain Quetiez
a3aed6aafc Printable view: cosmetics on object names (hyperlinks) in the actual print view -reverse merging a file that was NOT ready for committing
SVN:trunk[3692]
2015-08-20 14:15:02 +00:00
Romain Quetiez
6903a36298 Printable view: cosmetics on object names (hyperlinks) in the actual print view
SVN:trunk[3691]
2015-08-20 13:11:42 +00:00
Romain Quetiez
ea4c654af8 Printable view: do not show pagination controls (show the full list), must work with plugins calling cmdbAbstractObject::DiplaySet AND cmdbAbstractObjectObject::GetDisplaySet. Sill, GetDisplayExtendedSet should be hacked as well (?)
SVN:trunk[3690]
2015-08-20 08:07:42 +00:00
Romain Quetiez
ec61417e39 #1081 Customizations: adjust the dimensions of the HTML Editor (CKEditor). Also fixed an issue when specifying width/height with unit (e.g. "30em") for AttributeText/AttributeLongText
SVN:trunk[3689]
2015-08-19 17:10:28 +00:00
Romain Quetiez
3ba2c3d657 Log REST/JSON calls (config: 'log_rest_service' => true ; stored as EventRestService)
SVN:trunk[3688]
2015-08-19 14:35:08 +00:00
Romain Quetiez
8b5faf6b66 #564 Prompt for an update in a case log on a lifecycle transition. Can be retrofitted easily.
SVN:trunk[3687]
2015-08-19 12:42:49 +00:00
Romain Quetiez
76149633a1 #1074 Portal: errors when selecting Impact/Urgency, and if the user has access to his organization only.
SVN:trunk[3686]
2015-08-19 09:59:15 +00:00
Romain Quetiez
d8113a3304 #1130 CAS authentication security leak when cas_memberof is left empty (already committed into branch 2.1.0)
SVN:trunk[3685]
2015-08-18 13:48:12 +00:00
Romain Quetiez
3fc19bf160 Completion of [3668]: #1116 (and #1117): default values for ENUMs must always be expressed as strings.
SVN:trunk[3683]
2015-08-17 16:14:24 +00:00
Romain Quetiez
a30cb0b4c4 Export: for tabular exports, the label for the "friendly name" column must match the one recognized by CSV import (the very standard one, almost unused yet)
SVN:trunk[3682]
2015-08-17 15:28:19 +00:00
Romain Quetiez
d29e9b525d Export/CSV:
- Select the Character encoding (argument for CLI mode: charset)
- Make the correct column name for friendly names (ext key -> ext field)

SVN:trunk[3681]
2015-08-17 15:11:46 +00:00
Romain Quetiez
7c8a348ead Exports: Friendly names to be escaped and delimited in CSV (bug more exposed now that the export allows field selection)
SVN:trunk[3680]
2015-08-17 14:54:04 +00:00
Romain Quetiez
712931b728 #576 Printable view for object details. Possibility to hide/show chapters (the equivalent of tabs in the UI) or any fieldset. Requires testing and comments.
SVN:trunk[3679]
2015-08-17 14:12:36 +00:00
Denis Flaven
2f0b122101 Automatic installation of modules: remove duplicates if needed.
SVN:trunk[3678]
2015-08-14 12:42:51 +00:00
Denis Flaven
628b7644b7 Pan and zoom in the impact analysis view.
SVN:trunk[3677]
2015-08-14 12:38:50 +00:00
Denis Flaven
88717ac9ab Integration of the German translation provided by ITOMIG thanks to David Gümbel !
SVN:trunk[3676]
2015-08-14 12:18:19 +00:00
Romain Quetiez
e5e90b1faf Secure the server: prevent the users from browsing/getting files from the data and log directories. With Apache, it is still a must to enable htaccess with the spec "AllowOverride All". The index.php files are here to prevent from browsing whatever the HTTP server config.
SVN:trunk[3675]
2015-08-12 14:25:32 +00:00
Romain Quetiez
1a970d1372 #1120 Export V2 not working when using aliases (ex: SELECT Person AS p)
SVN:trunk[3674]
2015-08-12 10:02:37 +00:00
Romain Quetiez
b87b33c955 #1095 Object creation form and bulk modify (final step) not working when using apache-proxy
SVN:trunk[3673]
2015-08-12 09:21:19 +00:00
Denis Flaven
cfe9675709 #1118: fixed strange display of synchro data sources status.
SVN:trunk[3672]
2015-08-08 13:52:57 +00:00
Denis Flaven
b5f75271b9 #1121: Regression: "filters" on Triggers had no effect. The regression was caused by the new way of computing placeholders "on the fly" (#803).
SVN:trunk[3671]
2015-08-06 09:20:39 +00:00
Denis Flaven
90d5f5b8cf #1116 (and #1117): default values for ENUMs must always be expressed as strings.
SVN:trunk[3668]
2015-08-03 15:33:37 +00:00
Denis Flaven
f84f17a5be Fixed a potential XSS vulnerability.
SVN:trunk[3662]
2015-07-30 09:05:48 +00:00
Denis Flaven
ea8b254bd9 Readme updated due to the delayed release...
SVN:trunk[3661]
2015-07-28 14:16:52 +00:00
Denis Flaven
cb5f6e1ada Enhancement (internal) mark the "dict" entries as modified when loading them.
SVN:trunk[3660]
2015-07-28 12:55:51 +00:00
Denis Flaven
fa94dd257a File-based "transactions" dans log files better protected against concurrent access...
SVN:trunk[3659]
2015-07-28 12:51:46 +00:00
Denis Flaven
2a9ae8335d Use the new iTop logo (orange) in the portal as well.
SVN:trunk[3658]
2015-07-28 12:43:52 +00:00
Denis Flaven
567317386a - Fixed the "context" icons when displaying the impact analysis
- Bug fix: properly compute the list of impacted CIs on an Incident

SVN:trunk[3657]
2015-07-28 12:42:39 +00:00
Denis Flaven
24a54f146c Oups, one remaining typo in the German dictionary.
SVN:trunk[3656]
2015-07-28 12:36:47 +00:00
Denis Flaven
742abab420 Bug fix: typo causing the generation of invalid SQL queries (in some rare cases).
SVN:trunk[3653]
2015-07-28 12:25:19 +00:00
Denis Flaven
c1c3cd3dc9 #1099 and #1014: some German translations.
SVN:trunk[3652]
2015-07-28 12:14:08 +00:00
Denis Flaven
5e5739e37e Prepared the dictionaries for translating new entries (completed the french translation at the same time). Just look for the strings terminated by ~~ and translate them in place and you're done!
SVN:trunk[3651]
2015-07-28 11:30:31 +00:00
Denis Flaven
69c0bcd4ca Fixed typo in the french dictionary
SVN:trunk[3650]
2015-07-28 10:47:58 +00:00
Denis Flaven
d994bbffd0 Cosmetics on dict names (no spaces!)
SVN:trunk[3649]
2015-07-28 10:45:49 +00:00
Denis Flaven
8c5b020961 Enhancements to file based transactions (still experimental)
SVN:trunk[3648]
2015-07-28 10:40:53 +00:00
Denis Flaven
a426cf07e9 #1107: Make sure that all settings are preserved upon update.
SVN:trunk[3647]
2015-07-28 10:28:21 +00:00
Denis Flaven
d64641127a Fixed graphviz path check
SVN:trunk[3646]
2015-07-28 10:16:25 +00:00
Denis Flaven
cbc0e36057 Cosmetics on dict name
SVN:trunk[3645]
2015-07-28 10:13:45 +00:00
Denis Flaven
26405f8299 Make sure that the stylesheet can be loaded by the setup page at first run.
SVN:trunk[3644]
2015-07-15 15:15:55 +00:00
Denis Flaven
606e462b53 The path to Graphviz' dot program is now prompted interactively during the setup, since Graphviz is now mandatory for displaying the impact analysis.
SVN:trunk[3643]
2015-07-15 15:14:38 +00:00
Denis Flaven
d424addb4c Review of the readme for 2.2.0 beta.
SVN:trunk[3642]
2015-07-15 09:55:53 +00:00
Romain Quetiez
5427d6a466 Preparing the beta release: increment the module versions
SVN:trunk[3641]
2015-07-15 09:20:03 +00:00
Denis Flaven
b04298916c A little bit of polishing on the export feature to be ready for the beta.
SVN:trunk[3640]
2015-07-10 16:54:26 +00:00
Romain Quetiez
d3990ee2be Draft (the new features need to be further described)
SVN:trunk[3639]
2015-07-10 15:11:19 +00:00
Romain Quetiez
4e567585af #759 Ticket lists in CI: show only active tickets (exclude tickets in states rejected/resolved/closed) and display one list per leaf class so that the status column will be visible. It it not possible anymore to edit the ticket list from the CI.
SVN:trunk[3638]
2015-07-10 13:46:34 +00:00
Romain Quetiez
3bafb01202 Fixed bug with the new locking mecanism: lock not released when applying a stimulus
SVN:trunk[3637]
2015-07-10 13:33:01 +00:00
Romain Quetiez
dd5454591a Fixed "Strict standards" warning (introduced with the implementation of locks on edition)
SVN:trunk[3636]
2015-07-10 09:09:28 +00:00
Romain Quetiez
df9f25dc3c Optimization: improved the OQL cache:
- take benefit of the APC cache (if present)
- memory indexation could fail in case of long queries (query id based on a md5)
- added kpi measure on the OQL parsing

SVN:trunk[3635]
2015-07-09 14:37:29 +00:00
Romain Quetiez
a6b74d6538 Optimization: when displaying an object details, do not check data synchro for each and every attribute (the cache did exist but was inoperant)
SVN:trunk[3634]
2015-07-09 13:43:34 +00:00
Romain Quetiez
0b045e5dd0 Code cleanup: removed a code that was confusing while producing absolutely nothing
SVN:trunk[3633]
2015-07-09 07:49:04 +00:00
Romain Quetiez
7a139dddc0 Code cleanup: deprecated the unused (and empty) class CMDBSearchFilter, replaced by DBSearch or DBObjectSearch depending on the usage.
SVN:trunk[3632]
2015-07-09 07:36:39 +00:00
Romain Quetiez
baf54a7c02 #942 OQL now supporting unions. Unions support polymorphism and can be used anywhere in the application.
SVN:trunk[3631]
2015-07-08 17:10:40 +00:00
Denis Flaven
20e4dbfc1d A little bit of polishing on the impact analysis feature...
SVN:trunk[3630]
2015-07-08 15:56:34 +00:00
Denis Flaven
77388bed29 #714: localization of the date picker calendar. Get rid of the old jquery.datepicker.js file since iTop now relies on the built-in jQuery UI date picker widget.
SVN:trunk[3629]
2015-07-07 16:27:09 +00:00
Denis Flaven
cf5adc5ae7 #1062: bumped the version number of the REST/JSON API to 1.3 to be aligned with the documentation !
SVN:trunk[3628]
2015-07-07 14:00:47 +00:00
Denis Flaven
1070283349 #963: For security reasons, "Portal users" are no longer allowed to use the REST/JSON API.
SVN:trunk[3627]
2015-07-07 13:56:19 +00:00
Denis Flaven
4ee78ea59c #1078: Properly record the history of LinkedSet(Indirect)
SVN:trunk[3626]
2015-07-07 13:01:40 +00:00
Denis Flaven
b8f0ecb134 Bug fix: don't accept attachments (like images) via Chrome's copy/paste since it may duplicate the text content of a normal copy/paste and moreover causes troubles because there is no file name associated with the pasted content.
SVN:trunk[3621]
2015-07-06 14:28:36 +00:00
Denis Flaven
efec6f6ec9 Performance optimization: cache the result of the disk scan looking for icons for dashboards
SVN:trunk[3620]
2015-07-06 13:04:24 +00:00
Denis Flaven
7f460eda5a Better error reporting (thanks to Stefan Goethals for suggesting it).
SVN:trunk[3619]
2015-07-06 12:38:23 +00:00
Denis Flaven
678786c76c #765: prevent two persons to edit the same object at the same time. Typo.
SVN:trunk[3618]
2015-07-02 15:50:55 +00:00
Denis Flaven
9917d6355c #765: prevent two persons to edit the same object at the same time.
SVN:trunk[3617]
2015-07-02 15:40:39 +00:00
Romain Quetiez
7f65e9fd5e New lifecycle action SetCurrentPerson. Also improved the existing lifecycle action SetCurrentUser to prevent from calling it on an external key that is not pointing to users (!= contact), and if the target attribute is a string, then store the friendlyname there.
SVN:trunk[3616]
2015-07-02 09:43:15 +00:00
Denis Flaven
9f92e5e0be #788 Whenever a timeout is detected by an ajax request, a popup dialog warns the user to log-in again.
SVN:trunk[3613]
2015-06-25 15:32:30 +00:00
Denis Flaven
6e92438282 Bulk export: don't forget to cleanup in case of error.
SVN:trunk[3612]
2015-06-25 10:01:14 +00:00
Denis Flaven
f25980bb0d The Sass cache has nothing to do in SVN!
SVN:trunk[3611]
2015-06-23 16:32:37 +00:00
Denis Flaven
dd7861c5b4 Bulk Export redesign... finishing touch.
SVN:trunk[3610]
2015-06-23 16:29:45 +00:00
Denis Flaven
35a4112840 Small enhancement to the display of the meta model: in the list of transitions, display the code of the event as a tooltip.
SVN:trunk[3609]
2015-06-23 15:47:33 +00:00
Denis Flaven
2982f9cc9b Bulk Export redesign... change the menu to point to the new (interactive) export.
SVN:trunk[3608]
2015-06-23 14:59:35 +00:00
Denis Flaven
bbd83fba30 Bulk Export redesign... Typo in dict entry name.
SVN:trunk[3607]
2015-06-23 14:58:46 +00:00
Denis Flaven
cd5e5da526 Bulk Export redesign, addressing the tickets:
#1071 Bulk Read access rights
#1034 List of fields for Excel export
#772 Some attributes not exportedvia export.php
Main features:
- list and order of the fields taken into account
- interactive mode to specify all the parameters interactively (including the list and the order of fields)
- same behavior for all the formats: html, CSV, spreadsheet, XML
- new PDF export

SVN:trunk[3606]
2015-06-23 14:16:46 +00:00
Denis Flaven
f8df72b329 New look for iTop !
SVN:trunk[3602]
2015-06-22 08:14:25 +00:00
Denis Flaven
19e5130441 New look for iTop !
SVN:trunk[3601]
2015-06-20 15:02:24 +00:00
Denis Flaven
9ba1914524 Added an alternate implementation for storing "transaction" identifiers on disk instead of inside the $_SESSION variable.
SVN:trunk[3598]
2015-06-20 13:37:49 +00:00
Denis Flaven
586ec4515d Mutex instrumentation for troubleshooting...
SVN:trunk[3595]
2015-06-19 14:46:08 +00:00
Romain Quetiez
4c2543d6f4 JSON/REST: When specifying a case log entry (or the whole), it was not possible to set the user name without knowing a valid user id
SVN:trunk[3593]
2015-06-16 09:50:29 +00:00
Denis Flaven
1aa489890c Make sure that the SQL mutexes are specific to the current iTop instance, but still preserving the capability for the setup to detect an already running cron job with or without a valid config file.
SVN:trunk[3591]
2015-06-12 17:00:41 +00:00
Romain Quetiez
7a5bbd0613 Integrated the lexer/parser build tools (Lexer=0.4.0, Parser=0.1.7)
SVN:trunk[3590]
2015-06-10 13:23:03 +00:00
Denis Flaven
d9fcd83370 Impact analysis diagram is now considered as beta !
SVN:trunk[3588]
2015-05-26 16:39:51 +00:00
Romain Quetiez
73cd1274a5 Implemented GetForJSON and FromJSONToValue for AttributeLinkedSet (though this is not used for the Rest/JSON services which are doing much more) -retrofit from branch 2.1.0
SVN:trunk[3587]
2015-05-26 12:04:29 +00:00
Romain Quetiez
35e58f8cd2 Make it possible to overload RestUtils (static methods called with static:: instead of self::) - iTop NOW REQUIRES PHP 5.3: we have verified, there are very installations of iTop made on PHP 5.2. It is worth to note that PHP 5.3 is already end of life (5.4 will become end of life in 8 months)
SVN:trunk[3584]
2015-05-26 11:44:12 +00:00
Denis Flaven
0769b2c481 Relations & Impact analysis enhancements:
- Detailled tooltips in the graph
- Context queries ("knowing that")

SVN:trunk[3583]
2015-05-24 20:47:11 +00:00
Denis Flaven
80c0312219 Automatically save the PDF of the impact analysis as an attachement to the ticket.
SVN:trunk[3582]
2015-05-20 09:33:42 +00:00
Denis Flaven
3949632339 Removed unused function parameter.
SVN:trunk[3581]
2015-05-18 12:45:44 +00:00
Denis Flaven
1eb4b0cec4 Default value (=empty array) for excluded objects
SVN:trunk[3580]
2015-05-18 12:18:04 +00:00
Denis Flaven
a1ba5bec17 Impact analysis diagram uses jQuery context menus.
SVN:trunk[3579]
2015-05-18 12:17:16 +00:00
Denis Flaven
7ca7cb39ae Integration of the new impact analysis into the tickets.
SVN:trunk[3578]
2015-05-15 13:49:25 +00:00
Denis Flaven
d1a74589b1 More options for the PDF export of the 'impact' graph.
SVN:trunk[3577]
2015-05-10 09:07:19 +00:00
Denis Flaven
3e6896b8e6 Optimization of DisplayBlock::FromObjectSet, load only the needed column!
SVN:trunk[3576]
2015-05-06 17:13:30 +00:00
Romain Quetiez
3595434a05 XML Modelization of the relations: wrong computation of the upstream query (wrong computation of the redundancy when an Application Solution is made of CIS of various types)
SVN:trunk[3575]
2015-05-05 12:42:43 +00:00
Denis Flaven
7f1f1337fa Relation diagrams:
- Localization
- Handle the resize of the window
- Aysnchronous load/reload
- Filtering of the result based on the class

SVN:trunk[3574]
2015-05-05 08:04:23 +00:00
Romain Quetiez
7077879194 Enable queries on the synchronized objects (SynchroReplica::dest_id changed into an attribute of type AttributeObjectKey).
SVN:trunk[3573]
2015-05-04 09:36:22 +00:00
Romain Quetiez
f314036cef #1079 DBWriteLinks deleting related objects
SVN:trunk[3572]
2015-04-30 15:55:38 +00:00
Denis Flaven
af2835e505 Make sure that 'source' nodes for ComputedImpactedItems are not added twice to the ticket.
SVN:trunk[3571]
2015-04-29 17:27:40 +00:00
Denis Flaven
d63b4ef6d1 Integration of the new way to compute relations into the datamodel (ComputeImpactedItems)
SVN:trunk[3570]
2015-04-29 16:35:21 +00:00
Romain Quetiez
f69109bc43 #1069 (continuation of commit 3558) There may be some null values in the Database, making it impossible to upgrade. Defining a default value is far enough for external keys and hierarchical keys. Furthermore, this will be less time consuming during the setup (no need for table scans)
SVN:trunk[3569]
2015-04-29 15:31:28 +00:00
Romain Quetiez
255df92a30 Code refactoring: coloring a relation graph (purpose: distinguish potentially impacted CIs and really impacted CIs, when analyzing a change ticket)
SVN:trunk[3568]
2015-04-28 15:50:14 +00:00
Romain Quetiez
95defedf08 Improved the symptom when an error occurs in the "apply stimulus form". The symptom used to be: Object could not be written; unknown error. Now it will give the error message (e.g. Missing query arguments) so as to help in determining what's going on.
SVN:trunk[3567]
2015-04-27 09:39:41 +00:00
Romain Quetiez
ec97e6d2e0 ormStopWatch::GetElapsedTime not working in case of queries containing :this-> parameters (the prototype of GetElapsedTime has changed and is NOT compatible with the previous one)
SVN:trunk[3564]
2015-04-27 09:24:09 +00:00
Denis Flaven
161a92fef2 Impact analysis: migration to XML, bug fix for Server <=> Hypervisor
SVN:trunk[3563]
2015-04-24 10:47:42 +00:00
Romain Quetiez
da7ae0660e Fixed a regression introduced in [3518] (module parameters in XML)
SVN:trunk[3562]
2015-04-24 10:40:07 +00:00
Denis Flaven
ca794b421d Impact analysis: still an alpha version.
SVN:trunk[3561]
2015-04-24 10:10:51 +00:00
Romain Quetiez
520ccd361c Fixed a typo on the default document mimetype: application/x-octet-stream
SVN:trunk[3560]
2015-04-24 08:28:45 +00:00
Denis Flaven
aa93fde347 Impact analysis: still an alpha version.
SVN:trunk[3559]
2015-04-24 07:42:50 +00:00
Romain Quetiez
fedde33be1 #1069 Added a default value to the column definitions whenever possible: makes it less a pain to add a new hierarchical key when there are already some records in the DB
SVN:trunk[3558]
2015-04-23 17:15:07 +00:00
Romain Quetiez
dc356ae7b6 XML Modelization of the relations: XML definition moved so as to allow a minimal installation (no virtualization)
SVN:trunk[3557]
2015-04-23 17:12:07 +00:00
Romain Quetiez
fa333504c6 XML Modelization of the relations: no option to restrict the browsing to downstream
SVN:trunk[3556]
2015-04-23 10:11:33 +00:00
Denis Flaven
7c210f4d1c Replacement of the impact Flash based analysis graph by graphviz + Raphael + TCPDF. ALPHA version.
SVN:trunk[3555]
2015-04-23 10:03:18 +00:00
Denis Flaven
df47e2a9e9 Replacement of the impact Flash based analysis graph by graphviz + Raphael + TCPDF. ALPHA version.
SVN:trunk[3554]
2015-04-23 10:02:06 +00:00
Romain Quetiez
87a3b73024 XML Modelization of the relations: updated the conversion from 1.2 to 1.1
SVN:trunk[3553]
2015-04-23 09:33:04 +00:00
Romain Quetiez
eb379662ce Rework of the relation diagrams: configuration of the redundancy (AttributeRedundancySettings)
SVN:trunk[3552]
2015-04-22 15:33:07 +00:00
Denis Flaven
7176d5a19c Bug fix: prevent a crash of the web services when trying to log a non scalar paramater value...
SVN:trunk[3549]
2015-04-16 15:33:21 +00:00
Romain Quetiez
ff1514dc75 Modules implementing a lifecycle written in PHP (and having actions executed on transitions) do not work until 2.1.0. The compatibility patch had been implemented but it was not working. Good candidate for a retrofit to the branch 2.1.0
SVN:trunk[3547]
2015-04-16 13:49:36 +00:00
Romain Quetiez
59ebc262a3 Rework of the relation diagrams: added min_up data to the redundancy nodes
SVN:trunk[3546]
2015-04-15 14:00:05 +00:00
Romain Quetiez
26eb4c7083 Rework of the relation diagrams: implemented MetaModel::GetRelatedObjectsUp, and took the redundancy into account (still misses a GUI)
SVN:trunk[3545]
2015-04-15 09:06:50 +00:00
Romain Quetiez
ef8888c679 Rework of the relation diagrams: implemented MetaModel::GetRelatedObjectsDown (still not taking the redundancy into account)
SVN:trunk[3544]
2015-04-13 12:59:26 +00:00
Romain Quetiez
34ff5d6ac4 #1092 Caller not preset when creating a ticket from a contact
SVN:trunk[3543]
2015-04-10 12:02:23 +00:00
Romain Quetiez
e64b6d1d98 XML Modelization of the relations: reworked toward an asymetric definition (downstream: A impacts B, upstream: B depends on A)
- The queries are developped at runtime (cache)
- More complex algorithm to take into account the legacy type of specification (GetRelationQueries)
- New dictionary naming convention (preserving backward compatibility): "VerbUp" to be replaced by "DownStream
- Temporary hacks to preserve the relation 'depends on', until we have a new GUI
- Special handling for the relation LogicalVolume impacts VirtualDevice which had to be implemented in the bridge module
- Improved the backward compatibility by leaving legacy methods GetRelationQueries returning an empty definition, allowing for an eventual XML redefinition

SVN:trunk[3542]
2015-04-10 10:09:22 +00:00
Denis Flaven
b9b5287b37 Helper class to managed relation graphs.
SVN:trunk[3541]
2015-04-08 14:50:01 +00:00
Denis Flaven
5df6009f08 #1082 Dashlet badge: do not display search results everytime.
SVN:trunk[3539]
2015-04-07 13:19:11 +00:00
Denis Flaven
cca4737b91 #1088: support of HTMLEditor in the PortalWebPage, for example if the description of a ticket is in HTML.
SVN:trunk[3538]
2015-04-07 13:03:49 +00:00
Denis Flaven
bf1812ae83 Bug fix: properly compute the URLs/URIs for the soap server (and its extensions)
SVN:trunk[3536]
2015-04-07 09:55:50 +00:00
Denis Flaven
0000cfd234 #1083: HTML export: show a scroll bar when needed.
SVN:trunk[3535]
2015-04-03 09:36:40 +00:00
Denis Flaven
a876cd2186 #1059: fix for the Spanish localization first_name and last_name were swaped.
SVN:trunk[3534]
2015-04-03 09:00:57 +00:00
Denis Flaven
d9b1d0faf3 #1056: the 'zip' extension is now mandatory to install iTop, since the code relies on the ZipArchive class for the Excel export and the scheduled backup.
SVN:trunk[3533]
2015-04-03 08:50:03 +00:00
Denis Flaven
2856d53967 #1054: increase max_execution_time during the setup.
SVN:trunk[3532]
2015-04-03 08:37:55 +00:00
Denis Flaven
9772b58333 #1052: Fix for the German localization.
SVN:trunk[3531]
2015-04-03 08:30:40 +00:00
Denis Flaven
b74ab0614e Fixing a regression introduced by 3525
SVN:trunk[3530]
2015-04-03 08:28:15 +00:00
Denis Flaven
61a21520d1 #1050: Properly support the 'list' display style for external keys - as stated in the documentation!
SVN:trunk[3529]
2015-04-03 08:26:39 +00:00
Denis Flaven
ebfc9aa1e0 #1047: Fix for the FindTab method.
SVN:trunk[3528]
2015-04-03 08:02:20 +00:00
Denis Flaven
ff54d6dd6c #1045: Fix in the German localization.
SVN:trunk[3527]
2015-04-03 07:58:11 +00:00
Denis Flaven
828e4d6297 Oops, wrong commit, reverting these two files to their previous version.
SVN:trunk[3526]
2015-04-01 16:04:20 +00:00
Denis Flaven
030b4fa380 Enhancement: support injection of new modules treated as data.
SVN:trunk[3525]
2015-04-01 15:53:05 +00:00
Romain Quetiez
328a5e8077 XML Modelization of the relations: transformed the existing model (preserving the current behavior) to define the relations as an ATTRIBUTE whenever possible. Also took the opportunity to enforce a naming convention (neighbour id = target class name in lower case)
SVN:trunk[3524]
2015-03-30 15:38:37 +00:00
Romain Quetiez
e8cbb2d39d XML 1.2: handle the XML transformation. Added APIs to report the functionality loss when downgrading (snippets, portal, module parameters, relations and object key)
SVN:trunk[3523]
2015-03-30 14:17:29 +00:00
Romain Quetiez
887e73ea1d XML Modelization of the relations: declare the relations based on the XML (implicit declaration in XML, explicit in PHP -thus retrocompatible)
SVN:trunk[3522]
2015-03-30 14:14:26 +00:00
Romain Quetiez
e210996839 XML Modelization of the relations: fixed a bug in the compiler and transformed the datamodel files (2.x) into the latest 1.2 format
SVN:trunk[3521]
2015-03-30 08:24:45 +00:00
Denis Flaven
2ba3ab3057 Enhancement: PHP snippets inside the XML.
SVN:trunk[3520]
2015-03-27 17:16:40 +00:00
Romain Quetiez
3cf0fa3ee2 XML Modelization of the relations, with full support of the previous way (by implementing a method GetRelationQueries). Still, the standard data model has not been migrated to the new format.
SVN:trunk[3519]
2015-03-26 11:12:25 +00:00
Denis Flaven
8b36699893 Enhancement: the default value for a module's parameter can now be specified (and altered) via the XML and will no longer reside in the configuration file.
SVN:trunk[3518]
2015-03-25 15:11:24 +00:00
Denis Flaven
166f5ce73f Enhancement: allow the API to create entries with a specified user_login.
SVN:trunk[3514]
2015-03-24 17:05:50 +00:00
Denis Flaven
92baec128e #594: properly display attachments inside "properties" by closing the span and the fieldset in non-edit mode.
SVN:trunk[3510]
2015-03-23 17:52:17 +00:00
Denis Flaven
4919ca88ec Modularization of the portal. The entry points for portals is now defined in XML, and thus can be altered by an extension.
SVN:trunk[3509]
2015-03-23 16:02:44 +00:00
Romain Quetiez
fa0d408664 OQL enhancement: continuation... (bug fix with a query on object history)
SVN:trunk[3507]
2015-03-19 15:25:04 +00:00
Romain Quetiez
444d9e36c6 OQL enhancement: allow JOIN on a objclass/objkey pair of attributes (requires benchmarking)
SVN:trunk[3506]
2015-03-19 12:50:15 +00:00
Denis Flaven
95fc4d867d Fixed another regression of 3500: LongTextFields also support multiple forbidden lists...
SVN:trunk[3505]
2015-03-12 15:26:08 +00:00
Denis Flaven
6524a40eaa Enhancement: do not retrieve disabled fields.
SVN:trunk[3504]
2015-03-12 14:00:14 +00:00
Romain Quetiez
f53943e78c Meta information on lifecycle actions arguments: added type restrictions, and added the method ResetStopWatch
SVN:trunk[3503]
2015-03-12 10:42:51 +00:00
Denis Flaven
528a8901df Fixed a regression introduced by the revision 3500: the default value is NEVER "forbidden"
SVN:trunk[3502]
2015-03-11 15:46:53 +00:00
Denis Flaven
acd6d9679a Additional markup for JQuery scripts...
SVN:trunk[3501]
2015-03-11 15:33:01 +00:00
Denis Flaven
f7c7fc5dc4 Support several sets of forbidden values (with a specific "reason" message) per field.
SVN:trunk[3500]
2015-03-10 15:34:04 +00:00
Romain Quetiez
d575c48579 N°257 Aperçu des dashlets Badge partiellement hardcodé ("Search for objects of type Server")
SVN:trunk[3499]
2015-03-10 13:48:50 +00:00
Denis Flaven
930d833e1b #803: template placeholders are now built on demand. Yes !!
SVN:trunk[3498]
2015-02-27 10:02:44 +00:00
Denis Flaven
f7f77911be - Properly handle "suggested" attachments
- Properly pass the name of the uploaded file to the internal JS event

SVN:trunk[3496]
2015-02-12 17:59:08 +00:00
Romain Quetiez
508f82946f #1060 Internal: improved the symptoms when calling MetaModel::GetAttributeDef with an invalid attribute code (feedback on the class name and no more FATAL errors)
SVN:trunk[3492]
2015-02-09 13:11:49 +00:00
Romain Quetiez
6bb9754628 Internal: fixed the caching of DBObject::ToArgs()
1) Wasn't reset when the object was written the DB (thus having its ID set)
2) Wasn't taking the argument name into account (the list of placeholders was defined by the first caller)

SVN:trunk[3491]
2015-01-30 10:04:42 +00:00
Romain Quetiez
44fad50031 #1053 XML comments breaking the setup with message "Notice: Undefined property: DOMComment::$wholeText in ...modelfactory.class.inc.php on line 1280"
SVN:trunk[3490]
2015-01-14 13:51:37 +00:00
Denis Flaven
ed2cd2cea3 Change of the QueryReflection API to support DesignTime.
SVN:trunk[3489]
2015-01-12 14:20:20 +00:00
Romain Quetiez
eaf74a3f23 ModelFactory: Re-creating a class into another location in the class hierarchy it equivalent to moving that class => the delta must be a "redefine" for the class (improved the comment from the previous commit)
SVN:trunk[3487]
2015-01-08 11:05:18 +00:00
Romain Quetiez
1a99146b7a ModelFactory: Re-creating a class into another location in the class hierarchy it equivalent to moving that class => the delta must be a "redefine" for the class
SVN:trunk[3486]
2015-01-08 10:39:34 +00:00
Denis Flaven
4a8e9e71f4 Regression: the instance method is only available since jquery UI 1.11
SVN:trunk[3484]
2014-12-26 09:08:14 +00:00
Denis Flaven
6d2d0ff701 - Read-only "long text" fields no longer appear as editable
- Combo and FormSelector fields are now sorted by default (but sorting can be disabled if needed)

SVN:trunk[3483]
2014-12-23 15:23:12 +00:00
Denis Flaven
af3c93051f Protect against JS errors when the form is in read-only mode.
SVN:trunk[3482]
2014-12-23 14:49:06 +00:00
Denis Flaven
f594190005 Properly handle property_sheets with nested selector fields...
SVN:trunk[3481]
2014-12-23 13:42:25 +00:00
Denis Flaven
546d181ea9 Addition of the Danish localization contributed by Erik Bøg
SVN:trunk[3480]
2014-12-18 08:56:23 +00:00
Denis Flaven
143cefe4e3 #1041 Protect against some XSS injections
SVN:trunk[3479]
2014-12-18 08:50:04 +00:00
Romain Quetiez
ece152173f Advanced customization: a stop watch can be started in the past (incident ticket created from an alarm)
SVN:trunk[3478]
2014-12-18 08:37:00 +00:00
Denis Flaven
b08de31b3c Prevent duplicate declaration of the "Data Admin" menu (both in XML and PHP) which makes it impossible to customize.
SVN:trunk[3477]
2014-12-17 17:40:01 +00:00
Denis Flaven
0f967a41df Prevent a PHP crash when the icon tag is missing from a highlight_code definition in the XML.
SVN:trunk[3476]
2014-12-17 17:27:24 +00:00
Romain Quetiez
4c3bf70cc4 Completing [3423]: Problem/ev_assign still invoking the legacy verb SetAssignedDate
SVN:trunk[3475]
2014-12-17 08:55:02 +00:00
Romain Quetiez
35dd3f9610 The very final version (removed a misleading header in the readme file)
SVN:trunk[3473]
2014-12-16 15:13:09 +00:00
Romain Quetiez
a7f7424e54 #1039 Continuation of the fix implemented in [3465] that introduced a stopper regression (Fatal Error)
SVN:trunk[3472]
2014-12-16 13:54:40 +00:00
Denis Flaven
83e2974b10 #1040 Graphical display of "impact/depends on" is not consistent with the "list" tab
SVN:trunk[3471]
2014-12-16 13:40:51 +00:00
Romain Quetiez
715ba066d3 Adjusted dictionary entries (meta information about the lifecycle actions)
SVN:trunk[3470]
2014-12-16 09:02:03 +00:00
Romain Quetiez
9502003ff4 Updated the readme file with the latest changes
SVN:trunk[3469]
2014-12-15 16:11:15 +00:00
Romain Quetiez
57c827bb1a Updated the readme file with the latest changes
SVN:trunk[3468]
2014-12-15 16:08:50 +00:00
Denis Flaven
690ac9be75 #1038: dictionary cleanup to avoid misleading/duplicate names when importing Service Subcategories.
SVN:trunk[3467]
2014-12-15 16:08:10 +00:00
Romain Quetiez
4c3c31c44d Injectable methods: labels/descriptions given in the dictionary
SVN:trunk[3466]
2014-12-15 15:49:44 +00:00
Denis Flaven
3c9ace5b53 #1039: prevent concurrent executions of either synchro_import.php or synchro_exec.php for a given data source, since it would lead to unpredictable results.
SVN:trunk[3465]
2014-12-15 15:04:43 +00:00
Denis Flaven
bd5268dc42 Addition of the Ducth translation, thanks to Remie Malik.
SVN:trunk[3464]
2014-12-15 14:34:34 +00:00
Denis Flaven
133b6d4d29 #1037: refresh "priority" when either "impact" or "urgency" changes.
SVN:trunk[3463]
2014-12-15 14:09:17 +00:00
Denis Flaven
fba3990c61 - Proper handling of the validation hierarchy in property sheets.
- Correct behavior for animated submits...

SVN:trunk[3462]
2014-12-12 16:38:17 +00:00
Romain Quetiez
53e997cfba Instrumented Model Factory with means to keep track of touched nodes
SVN:trunk[3461]
2014-12-12 12:17:43 +00:00
Denis Flaven
e738ba35b7 The FormSelectorField now has its own widget to properly cope with its "subfields" in "property sheet" mode (continued).
SVN:trunk[3460]
2014-12-10 17:11:45 +00:00
Romain Quetiez
0773455ebc Cosmetics on the module names (consistency)
SVN:trunk[3459]
2014-12-10 10:48:47 +00:00
Denis Flaven
cafc6a8baf The FormSelectorField now has its own widget to properly cope with its "subfields" in "property sheet" mode.
SVN:trunk[3458]
2014-12-10 10:44:26 +00:00
Romain Quetiez
48f222df0b When adding a case log, existing objects could not be displayed anymore!
SVN:trunk[3457]
2014-12-09 16:07:06 +00:00
Denis Flaven
88726a0634 Support for some (optional) feedback during submit.
SVN:trunk[3456]
2014-12-08 13:19:09 +00:00
Denis Flaven
0ac522fc4c Support for some (optional) feedback during uploads.
SVN:trunk[3455]
2014-12-08 13:18:06 +00:00
Denis Flaven
aa97703b64 Add the appropriate "type" to the parameters passed to the actions on transitions.
SVN:trunk[3454]
2014-12-05 11:02:32 +00:00
Romain Quetiez
30af416394 Rework of the dictionaries: moved menu related entries to the module itop-welcome-itil (which does create most of those menus), while preserving the original copy of the entries so as to be compatible with customizations made with a copy of an older version of itop-welcome-itil
SVN:trunk[3453]
2014-12-04 16:05:11 +00:00
Denis Flaven
1f2ad9ecdb Demo mode: prevent the deletion of Users...
SVN:trunk[3452]
2014-12-04 10:02:14 +00:00
Romain Quetiez
d1f1889c42 Updated the release note with the changes made since the beta release
SVN:trunk[3451]
2014-12-03 16:43:28 +00:00
Romain Quetiez
ca7ee0f138 #1020 Restrict dashboard/shortcut refresh interval
SVN:trunk[3448]
2014-12-03 11:38:19 +00:00
Denis Flaven
909729e9f1 #1028: drop the support of non-numeric primary keys for DBObjects. This makes the queries related to the history (and the indexes) much more efficient. Beware, conversion time at setup can be long if the priv_changeop table is big.
SVN:trunk[3447]
2014-12-03 11:01:29 +00:00
Denis Flaven
a222ba998d Enhanced reporting during the setup: all the queries (create table / alter table) are now logged into "setup.log" along with their execution time.
SVN:trunk[3446]
2014-12-03 10:58:39 +00:00
Romain Quetiez
aedddf9dcc #1026 CSV import of tickets with impact = '', issuing a Notice
SVN:trunk[3445]
2014-12-02 14:45:19 +00:00
Denis Flaven
74de0d33ab #1021: fix alignment of multiple header dashlets in the same cell.
SVN:trunk[3444]
2014-12-02 13:10:44 +00:00
Romain Quetiez
8d8510a412 #1030 Missing values in the history tab (TTO/TTR)
The regression has been introduced in iTop 2.0.2 with the fix for #703 (escape HTML entities). There is no data loss: changes were correctly made and all the changes already recorded will be correctly displayed with the current fix.
The current change consists in a little rework: GetAsHTMLForHistory has been renamed DescribeChange, and it now calls oAttDef->GetAsHTMLForHistory instead of calling oAttDef->GetAsHTML, thus allowing for the factorization of the code that format the change description. AttributeSubitem does not overload DescribeChange. Rather, it simply overloads GetAsHTMLForHistory (viewing the diff may be confusing here).

SVN:trunk[3443]
2014-12-02 10:53:59 +00:00
Denis Flaven
4dd83a0eb6 #985: preserve the displayed sort order when refreshing a table.
SVN:trunk[3442]
2014-12-02 10:26:26 +00:00
Romain Quetiez
671ee353e8 #1027 Regression introduced in [3148] thus in 2.0.3 (cache the reconciliation for external keys on the CSV import) a cache hit on an ambiguous external key was not correctly handled
SVN:trunk[3440]
2014-12-01 19:12:19 +00:00
Denis Flaven
d714235d67 #1016: record the displayed value of the database_table_name field in the database.
This happens:
- when creating a new Synchro Data Source
- when modifying an "old" Synchro Data Source for which the field was empty.

SVN:trunk[3439]
2014-12-01 17:33:38 +00:00
Romain Quetiez
c4b039c9c6 #975 Continuation of the fix implemented in [r3431] - Renaming an enum requires to call RenameEnumValueInDB
SVN:trunk[3438]
2014-12-01 15:19:48 +00:00
Romain Quetiez
29e751278e #1029 Got rid of tags <format> that were not used at all and that were really misleading extension developers
SVN:trunk[3437]
2014-12-01 11:43:12 +00:00
Denis Flaven
87ed5d4a05 Fix for a regression introduced by [r3394] while fixing #1000: handling of OPT_ATT_MUSTCHANGE. The fix was not working when the original value contained several lines.
SVN:trunk[3436]
2014-11-28 17:03:13 +00:00
Denis Flaven
c8a20d01da Bug fix: proper handling of the validation of subforms...
SVN:trunk[3435]
2014-11-28 14:03:21 +00:00
Erwan Taloc
47add0eeea Allow linkage of organization to a Delivery model, directly from the tab "Customers"
SVN:trunk[3434]
2014-11-28 10:28:36 +00:00
Denis Flaven
2a9f69d70e More meta information about the interfaces.
SVN:trunk[3433]
2014-11-27 16:22:05 +00:00
Romain Quetiez
e90e570469 Datamodel version is now 2.1.0
SVN:trunk[3432]
2014-11-27 15:23:51 +00:00
Erwan Taloc
b0e56e5897 modify enumerated list for model type in order to manage Phone CIs : tack #975
SVN:trunk[3431]
2014-11-26 18:56:51 +00:00
Erwan Taloc
c96842d82c replace provider_name by provider_id in the search form of service-subcategories
SVN:trunk[3430]
2014-11-26 18:34:51 +00:00
Erwan Taloc
bc79aecd73 Improve french translation
SVN:trunk[3429]
2014-11-26 18:33:40 +00:00
Erwan Taloc
99755ad7e8 update highlight scale for request-management modules
SVN:trunk[3428]
2014-11-26 18:32:38 +00:00
Erwan Taloc
4867461f69 Add tab to link a problem to incidents
SVN:trunk[3427]
2014-11-26 17:38:53 +00:00
Erwan Taloc
3d21eecbba Translate missing tabs translation related_requests_list
SVN:trunk[3426]
2014-11-26 17:35:32 +00:00
Romain Quetiez
b822cff269 Instrumented the code to ease the troubleshooting of the computing of working hours
SVN:trunk[3425]
2014-11-25 15:25:28 +00:00
Romain Quetiez
3aa0b77751 Cosmetic on the XML: identify lifecycle functions parameters
SVN:trunk[3424]
2014-11-19 14:15:10 +00:00
Romain Quetiez
f4b10d3e81 #1022 Do cascade the resolution of an incident to its child requests + rework of the lifecycle/actions to ease the extensibility (New handlers: Rest, Copy, SetCurrentDate, SetCurrentUser, SetElapsedTime)
SVN:trunk[3423]
2014-11-19 09:44:52 +00:00
Denis Flaven
ca90f9b32a Oops, typo in "Prevent the JS validation (on focus) to create multiple entries for the same field, since it breaks the validation."
SVN:trunk[3422]
2014-11-18 17:25:08 +00:00
Denis Flaven
9ca051d9d0 Prevent the JS validation (on focus) to create multiple entries for the same field, since it breaks the validation.
SVN:trunk[3421]
2014-11-18 15:31:51 +00:00
Romain Quetiez
291f05683c New function: ormStopWatch::GetElapsedTime to compute the cumulated elapsed time on a stop watch still running -not used yet (but tested!)
SVN:trunk[3420]
2014-11-18 14:54:10 +00:00
Denis Flaven
4889ed5ac2 Read-only mode for icon selector widget: display at least the icon.
SVN:trunk[3419]
2014-11-18 09:22:48 +00:00
Denis Flaven
3fa354d00d Predefined objects are now handled by RuntimeEnvironment
SVN:trunk[3418]
2014-11-14 10:43:24 +00:00
Denis Flaven
f62934829d Fix of read_only flags for some attributes (previous verison had typos, i.e. "readonly" instead of "read_only", which made the factorisation of the state flags incorrect)
SVN:trunk[3416]
2014-11-10 13:58:37 +00:00
Denis Flaven
dee7f7b7aa Upgrade to Raphael 2.1.2
SVN:trunk[3415]
2014-11-07 13:25:37 +00:00
Romain Quetiez
1d0bfa7c92 XML format conversion utilities: no version means '1.0'
SVN:trunk[3414]
2014-11-06 15:51:08 +00:00
Denis Flaven
8922c435b4 Setup wizard cosmetics: added a "Reload" button to refresh the first page of the wizard when the prerequisites are not met.
SVN:trunk[3413]
2014-11-06 10:41:03 +00:00
Denis Flaven
ec1ec854fb Compiler: support "OPT_ATT_NORMAL" flags when an empty set of flags is defined in order to reset the flags on an attribute. Useful for inherited flags.
SVN:trunk[3412]
2014-11-06 10:39:44 +00:00
Romain Quetiez
dfc248b836 XML format conversion utilities: reworked the API to implement a CheckConvert and handle any kind of upgrade/downgrade (easy way to add a new format like 1.2)
SVN:trunk[3411]
2014-11-06 10:14:41 +00:00
Denis Flaven
481d05d082 Slightly better wording for describing the relations on the impact/dependency graph.
SVN:trunk[3410]
2014-11-05 18:30:39 +00:00
Denis Flaven
4e4d3cf3da Enhanced version of the SWF navigator providing a better stability for big numbers of elements.
SVN:trunk[3409]
2014-11-05 18:28:54 +00:00
Denis Flaven
5fc68557dc Enhanced version of the SWF navigator providing a better stability for big numbers of elements.
SVN:trunk[3408]
2014-11-05 18:27:56 +00:00
Denis Flaven
7482a52fd9 Reverting to revision 3397
SVN:trunk[3407]
2014-11-03 16:12:11 +00:00
Romain Quetiez
50ca6cdd0f Backup tools disabled when the demo mode is active
SVN:trunk[3406]
2014-11-03 15:12:47 +00:00
Romain Quetiez
e83e1262a5 Configuration editor disabled when the demo mode is active
SVN:trunk[3405]
2014-11-03 15:12:07 +00:00
Romain Quetiez
51bd403638 Draft of the readme file for 2.1.0 beta
SVN:trunk[3404]
2014-11-03 11:17:40 +00:00
Denis Flaven
c786e8308a Incrementation of the version number of the 2.x modules to reflect the change of the XML format.
SVN:trunk[3403]
2014-11-03 09:21:03 +00:00
Romain Quetiez
a7d3a5a488 New functionality: data backup / restore. By default, performs a daily backup, keeping the five last backups. Allows manual backups too.
SVN:trunk[3402]
2014-11-03 09:18:54 +00:00
Romain Quetiez
2c70c60d2b New functionality: a rudimentary configuration editor (admins only)
SVN:trunk[3401]
2014-11-03 09:17:27 +00:00
Denis Flaven
96f02d1557 Integration of the Excel (XLSX) export feature... (regression fix)
SVN:trunk[3400]
2014-11-01 07:46:57 +00:00
Denis Flaven
de6ddffe65 #998 Accurately check the configured mail transport and report information accordingly in the email.test page.
Add two "debug" transports for Swift mailer: Null and LogFile which are useful for staging environments where one does not want to send real emails.

SVN:trunk[3399]
2014-10-31 21:14:55 +00:00
Denis Flaven
2c59fb894f Integration of the Excel (XLSX) export feature. (Limitation: export.php takes into account neither the "fields" parameter nor the list of fields defined in the QueryPhrasebook when exporting in XLSX format)
SVN:trunk[3398]
2014-10-31 17:59:14 +00:00
Romain Quetiez
77cf399c72 Portal: handle mandatory attributes in Reopen/Close dialogs
SVN:trunk[3397]
2014-10-31 15:42:13 +00:00
Romain Quetiez
19a2180c2b #1000 Implemented the behavior for the flag OPT_ATT_MUSTCHANGE, and took the opportunity to add a feedback when a field is mandatory OR when the format is wrong
SVN:trunk[3394]
2014-10-31 14:55:12 +00:00
Romain Quetiez
18b73de512 #1012 Losing half of the connection when changing a port (connections between network devices). I took the opportunity to simplify the connection management as it was initiated in change [3388].
SVN:trunk[3393]
2014-10-30 10:53:30 +00:00
Denis Flaven
bb741c39f4 #1011 Proper resizing of the dialog box for managing 1:n links - removed a trace.
SVN:trunk[3390]
2014-10-30 08:54:48 +00:00
Denis Flaven
c73ef6ae72 #1011 Proper resizing of the dialog box for managing 1:n links.
SVN:trunk[3389]
2014-10-30 08:52:00 +00:00
Romain Quetiez
5abd9c6dad #1008 Error when deleting a Network Device connected to another Network Device (does not happen if the other end is another type of "ConnectedDevice")
SVN:trunk[3388]
2014-10-29 20:44:24 +00:00
Romain Quetiez
a6db04bafd #1007 Unexpected change of the case log when doing massive update of a User Request (+ hide the checkbox for the status because it makes no sense)
SVN:trunk[3387]
2014-10-29 15:46:44 +00:00
Romain Quetiez
04f46f4798 #979 Data synchro: recover the DB triggers (backup/restore). I've tested various combinations of the previous check (new columns to synchronize) and the new one (missing triggers).
SVN:trunk[3386]
2014-10-29 13:15:36 +00:00
Romain Quetiez
5ecf6b9e9d Fixed regression introduced in [3224] thus in iTop 2.0.3, in the data model view: could not see the OQL constraints on external keys
SVN:trunk[3385]
2014-10-28 16:07:42 +00:00
Romain Quetiez
0135573956 #995 Make sure that tto/ttr passed gets set even if the CRON has not been run (and as soon as some overrun has been counted)
SVN:trunk[3384]
2014-10-28 15:55:56 +00:00
Romain Quetiez
a59915e5ee Completing the change [3291]: the cron could not trigger a timeout on the stop watches, and the 'persistent' flag was forcibly set to true (whereas we expect the default value to be false!)
SVN:trunk[3383]
2014-10-28 15:51:55 +00:00
Denis Flaven
ebb3767ee4 RenderAsDialog now takes into account its "width" parameter.
SVN:trunk[3382]
2014-10-28 14:49:15 +00:00
Romain Quetiez
047166f002 #983 Sortering not possible on multi-column queries
SVN:trunk[3381]
2014-10-28 10:47:00 +00:00
Romain Quetiez
f45c783396 #965 Since 2.0.3, for each synchronized object, around 100 queries are performed (2 are required), and this is multiplied be the number of duplicate replicas (then resulting in a significant slowdown).
SVN:trunk[3380]
2014-10-28 08:53:30 +00:00
Denis Flaven
72f516685e #974 prevent multiple javascript refresh when reloading the "initial state" of a ticket.
SVN:trunk[3379]
2014-10-27 16:37:52 +00:00
Romain Quetiez
2b4400c55d XML: normalized the format (allows for implementing a quick diff algorithm)
SVN:trunk[3378]
2014-10-27 16:13:38 +00:00
Romain Quetiez
1df87b6331 #969 XML: the menu option enable_admin_only hides the menu for everyone
SVN:trunk[3377]
2014-10-23 19:22:18 +00:00
Romain Quetiez
cd7490472e #970 and #650 Corrupted attachements. Reworked the cleanup of undesired output, to protect it against the case when the output buffer is unfortunately closed. On the other hand, I found out that several output buffer can be stacked. Thus the protection could be further improved (difficulty: that can be web server dependent).
SVN:trunk[3376]
2014-10-23 15:48:49 +00:00
Romain Quetiez
b65131e4f5 #971 XML: could not specify an icon as a path to a file
SVN:trunk[3375]
2014-10-23 08:54:39 +00:00
Romain Quetiez
fac8604cc6 #972 Incomprehensible error message during setup, with a sample extension provided by Combodo! (empty user rights tag) -This fix improves a lot, but the final fix should be to compile user rights in a separate directory. To be further discussed.
SVN:trunk[3374]
2014-10-22 15:31:53 +00:00
Romain Quetiez
e25da2a7c4 Complete the commit [3291] that changes the structure returned by MetaModel::EnumTransitions. I have carefully reviewed every call to EnumTransitions... and found out only one single missing case in the data model view.
SVN:trunk[3373]
2014-10-22 10:44:58 +00:00
Romain Quetiez
6d9e7f690f #993 The about box does not show up when the directory extensions is missing
SVN:trunk[3372]
2014-10-22 09:48:13 +00:00
Denis Flaven
9c62952743 Typo: sKey => iKey.
Prefix for the dialogs to prevent a collision of IDs

SVN:trunk[3371]
2014-10-21 14:13:49 +00:00
Romain Quetiez
03c4964072 Protected the property fields against the collision of ids within the same page (even if that is a bug, make it work not too bad!)
SVN:trunk[3370]
2014-10-20 15:15:39 +00:00
Denis Flaven
711949414d Fixed the support of a non-default port for MySQL, thanks to theBigOne!
SVN:trunk[3368]
2014-10-16 14:32:10 +00:00
Romain Quetiez
055a87306e Forms: drop-down box default value label could be changed (or this entry could be entirely removed)
SVN:trunk[3367]
2014-10-16 10:15:41 +00:00
Romain Quetiez
37ebb51a2b Computation of user rights: added a config flag to force the legacy algorithm (user_rights_legacy, defaulting to false)
SVN:trunk[3366]
2014-10-16 10:14:17 +00:00
Romain Quetiez
1f8d4d379f Computation of user rights: deny on a parent class must give DENY even if the class is explicitely ALLOW on the same profile (that was already working if the rules are given on several profiles). Note that this has a cost when building the grant matrix!
SVN:trunk[3365]
2014-10-16 09:02:47 +00:00
Romain Quetiez
1300811007 Aligned the compiler with the new XML format (1.1):
- Profile actions (was buggy)
- Sort the states declaration (could be buggy depending on the order of the declaration withing the XML)

SVN:trunk[3364]
2014-10-15 15:07:52 +00:00
Denis Flaven
fbdd0dfd57 - Migration of the XML files to the version 1.1 of the XML schema.
- Refactoring of the "state flags" definitions to take advantage of the "inherit_flags_from" capability available in the new XML schema.

SVN:trunk[3363]
2014-10-14 09:56:21 +00:00
Denis Flaven
bc79663a3e Split in a separate "tool" class the upgrade of the format of the datamodel from 1.0 to 1.1.
Marked the XML as version 1.1.

SVN:trunk[3362]
2014-10-13 14:42:55 +00:00
Romain Quetiez
570e4f8589 New XML format, updated the transformation tool (UpgradeDocument)
SVN:trunk[3361]
2014-10-10 08:22:51 +00:00
Romain Quetiez
472802e11b Cosmetic improvement of an error message (data model compilation - collision of ids)
SVN:trunk[3360]
2014-10-07 14:41:11 +00:00
Denis Flaven
f794d0222e Forms enhancements and XML format touch-up.
SVN:trunk[3359]
2014-10-03 09:56:20 +00:00
Denis Flaven
b42a43d47b #968 (continued) do not loose the "advanced mode" flag in the interactive display.
SVN:trunk[3357]
2014-10-02 09:36:32 +00:00
Romain Quetiez
110aace270 Form fields: added callbacks ('equals' and 'get_field_value') to allow the implementation of enhanced form fields
SVN:trunk[3356]
2014-10-01 12:17:15 +00:00
Romain Quetiez
68dd0513c6 #968 Interactive CSV Export truncated or missing characters (since 2.0.3)
SVN:trunk[3354]
2014-09-25 15:21:20 +00:00
Romain Quetiez
08e757a08a #991 CSV export truncated (system dependent, since 2.0) due to a bug in iconv, the workaround is to do little by little
SVN:trunk[3352]
2014-09-25 15:01:45 +00:00
Romain Quetiez
996c590793 Code cleanup: reworked the user rights data model, while strictly preserving the current functionality (checked using the tool dump_profiles.php, with simple to full ITIL configurations). Class groups have been renamed/merged/removed. This is documented in the migration notes (wiki).
SVN:trunk[3351]
2014-09-24 09:18:14 +00:00
Romain Quetiez
c93cc3a5bf lnkVirtualDeviceToVolume and lnkTriggerAction are link classes and should be declared as such
SVN:trunk[3350]
2014-09-24 09:06:30 +00:00
Romain Quetiez
bdccf6ea72 Transmission of user rights along N-N links: must work both with DEL_AUTO and DEL_SILENT external keys (found with a code review, DEL_SILENT is still rarely used)
SVN:trunk[3349]
2014-09-24 08:56:08 +00:00
Romain Quetiez
ed60346ae6 Code cleanup (removed dead and misleading code branches), following revision [3347]
SVN:trunk[3348]
2014-09-23 13:38:22 +00:00
Denis Flaven
4953ea7701 Rework of the ModelFactory API to make it simpler and safer.
SVN:trunk[3347]
2014-09-22 15:19:56 +00:00
Denis Flaven
82a8a0bba7 #932: fixed a regression introduced by [r3319]... in case a criteria is present several times.
SVN:trunk[3344]
2014-09-17 15:41:35 +00:00
Romain Quetiez
55c818b6b7 Dehardcoded the size of the attachments preview
SVN:trunk[3343]
2014-09-16 12:26:50 +00:00
Romain Quetiez
372c0835f7 #988 Could not change the case of a login (cosmetic improvement to make sure this piece of code is the right example that can be shared amongst the developpers community)
SVN:trunk[3342]
2014-09-16 10:16:10 +00:00
Denis Flaven
52a028301f Bug fix: FetchAssoc was broken when dealing with in-memory sets.
SVN:trunk[3340]
2014-09-16 09:45:39 +00:00
Romain Quetiez
61b88d2689 #989 Developper issue: query arguments having a null value are dismissed
SVN:trunk[3339]
2014-09-16 09:45:17 +00:00
Romain Quetiez
fa856c32cd #988 Could not change the case of a login
SVN:trunk[3338]
2014-09-16 08:04:37 +00:00
Romain Quetiez
6d693bbfc7 #778 Issue on list sort order when editing an element - fixed a regression
SVN:trunk[3335]
2014-09-15 16:06:46 +00:00
Romain Quetiez
7117b751b3 #778 Issue on list sort order when editing an element (N-N link tabs)
SVN:trunk[3322]
2014-09-15 13:40:56 +00:00
Romain Quetiez
c30a88c9ef #986 Search form: handle indirect external keys
SVN:trunk[3321]
2014-09-15 10:01:19 +00:00
Romain Quetiez
c1085fc398 #987 Usage login prevents from user deletion
SVN:trunk[3320]
2014-09-12 12:58:18 +00:00
Romain Quetiez
42ac871f8e #932 Search form should be prefilled when running a search "shortcut" - very little progress: fixed the case when several criteria are given
SVN:trunk[3319]
2014-09-12 09:37:25 +00:00
Romain Quetiez
b1a404d909 #985 Shortcut auto refresh degrading table cosmetics
SVN:trunk[3318]
2014-09-12 08:18:06 +00:00
Romain Quetiez
ba82031a59 #984 Dashboard auto refresh degrading table functionalities like sorting
SVN:trunk[3317]
2014-09-12 08:15:14 +00:00
Romain Quetiez
c8568af43b #976 The fix [3306] introduced a regression: Call to undefined function DBInsert()
SVN:trunk[3315]
2014-09-11 08:17:37 +00:00
Romain Quetiez
7ed60f711c mproved the processing of background task to enable more advanced functionalities like queuing (protection against reentrance)
SVN:trunk[3313]
2014-09-03 09:53:55 +00:00
Romain Quetiez
e831b1a486 #978 Email test utility always reporting "default SMTP port"
SVN:trunk[3312]
2014-09-01 09:09:32 +00:00
Romain Quetiez
b5db86472d Fixed bug in the dashboard forms.
SVN:trunk[3311]
2014-08-29 12:54:55 +00:00
Denis Flaven
ed9ba815ab #976: (removed some debug traces) make sure that we do not bypass the method that computes the reference for newly created tickets.
SVN:trunk[3307]
2014-08-28 15:56:25 +00:00
Denis Flaven
7b47e8c480 #976: make sure that we do not bypass the method that computes the reference for newly created tickets.
SVN:trunk[3306]
2014-08-28 15:53:02 +00:00
Romain Quetiez
d2cd758ecc Automatically add an id on the user rights profile/actions to allow a finer granularity for the deltas.
SVN:trunk[3305]
2014-08-28 15:52:05 +00:00
Denis Flaven
d2ea4023c2 Protect dashboards against invalid queries in "grouped by" dashlets.
SVN:trunk[3304]
2014-08-28 12:07:26 +00:00
Romain Quetiez
f0ee5112b9 Improved the processing of background task to enable more advanced functionalities like queuing (factorized the error handling)
SVN:trunk[3302]
2014-08-21 08:53:16 +00:00
Romain Quetiez
ada70b97d4 Improved the processing of background task to enable more advanced functionalities like queuing
SVN:trunk[3300]
2014-08-19 10:08:35 +00:00
Denis Flaven
a546b39301 Support of more sophisticated forms layout...
SVN:trunk[3299]
2014-08-16 16:17:12 +00:00
Denis Flaven
8f2cd66bf6 Declaration of generic methods which can be run on tickets.
SVN:trunk[3298]
2014-08-14 15:46:43 +00:00
Denis Flaven
868d0d1b19 Renamed the "
SVN:trunk[3297]
2014-08-13 15:04:42 +00:00
Denis Flaven
7d619f278e Added <inherit_flags_from> to inherit the flags from another state.
SVN:trunk[3296]
2014-08-13 15:04:25 +00:00
Denis Flaven
fd9008a163 New concept in the XML: HighlightScale to avoid overloading GetIcon and GetHilightClass...
SVN:trunk[3291]
2014-07-28 15:16:16 +00:00
Denis Flaven
357ae4abb1 Automatically add an id on the transitions to allow a finer granularity for the deltas.
SVN:trunk[3287]
2014-07-24 12:46:56 +00:00
Denis Flaven
2108a74f87 Added an identifier on the transitions to allow a finer granularity for the deltas.
SVN:trunk[3286]
2014-07-24 12:42:40 +00:00
Romain Quetiez
165d01e2a2 Legacy user rights management: allow the deletion of a profile in one step (it was nearly impossible because of the numerous related records, mainly of type URP_ActionGrant, for which iTop was requesting a manual deletion)
SVN:trunk[3277]
2014-07-16 10:48:08 +00:00
Denis Flaven
4c0e176f3e #960 [RPM Packaging] Adjust line endings in READ and LICENSE files
#962 [RPM Packaging] Added the use of logrotate for cron.log and error.log

SVN:trunk[3274]
2014-07-15 09:21:47 +00:00
Denis Flaven
f0f519e351 #959 Fixing licensing mismatches for compatibility with the Fedora licensing policy (the modification only affects comments) .
SVN:trunk[3273]
2014-07-15 08:16:31 +00:00
Denis Flaven
9a68890660 Integration of the additions to the Russian localization by Vladimir Kunin.
SVN:trunk[3272]
2014-07-15 08:02:49 +00:00
Denis Flaven
806ed2102c [RPM Packaging] Preparing for the 2.0.3 release.
- Added php-ldap to the list of dependencies

SVN:trunk[3271]
2014-07-14 18:54:04 +00:00
Denis Flaven
14b9778c50 [RPM Packaging] small fix in the install script to adjust the rights in /etc/httpd/conf.d
SVN:trunk[3270]
2014-07-14 18:49:27 +00:00
Denis Flaven
0142910c26 #958 [RPM Packaging] Integrated Igor's patch to support both Apache 2.2 and 2.4.
SVN:trunk[3269]
2014-07-14 17:26:04 +00:00
Romain Quetiez
0068d7c8be Rolling back on the exclusion of portal users from the REST/JSON API. Let's communicate on this future change and leave some time for integrators to get ready for the change.
SVN:trunk[3268]
2014-07-11 14:37:55 +00:00
Romain Quetiez
302811986e Update for the upcoming release 2.0.3
SVN:trunk[3267]
2014-07-11 14:23:11 +00:00
Romain Quetiez
f773433a93 New REST service: core/check_credentials to implement single sign-on solutions
SVN:trunk[3266]
2014-07-11 14:21:30 +00:00
Denis Flaven
880653f191 Fixed a regression introduced by [r3242] (warning when sending an email with an empty "To")
SVN:trunk[3265]
2014-07-11 13:27:13 +00:00
Romain Quetiez
02ee41ef09 Readme ready for the Release Candidate of 2.0.3
SVN:trunk[3264]
2014-07-10 13:00:42 +00:00
Denis Flaven
d6344568c7 Update of the SwiftMailer licence
SVN:trunk[3263]
2014-07-10 12:51:09 +00:00
Denis Flaven
38a2fd75c0 Prevent a warning when sending an email with no recipient in To:
SVN:trunk[3262]
2014-07-10 12:49:39 +00:00
Denis Flaven
7ca8c3e834 One small step for making the portal more generic...
SVN:trunk[3261]
2014-07-10 12:48:00 +00:00
Romain Quetiez
7d3a93c374 #955 Translated the label "Open Requests" in the welcome menu
SVN:trunk[3260]
2014-07-10 10:29:44 +00:00
Denis Flaven
a978bb9c17 CSV import: "Ignore this field" can be used more than one time in the mapping.
SVN:trunk[3259]
2014-07-10 08:44:52 +00:00
Denis Flaven
3ed01cde51 Fixed regression introduced by "Provide feedback while loading asynchronous tabs.", provide feeback ONLY when loading!
SVN:trunk[3258]
2014-07-10 08:34:01 +00:00
Denis Flaven
67471e2636 Fix for a warning about mysql_free() revealing a problem with DBObjectSet
SVN:trunk[3257]
2014-07-10 08:27:16 +00:00
Denis Flaven
4c44159a88 Global search finalization:
- Skip abstract classes, since we search in all the child classes anyway
- Search in a class explicitely requested even it should be skipped by config
- Remove the "no object found" message if the "enlarged search" finds something

SVN:trunk[3256]
2014-07-09 10:32:30 +00:00
Romain Quetiez
5f5125a131 #854...ROLLBACK, undoing the commit [3185]. The implementation has been postponed to the next major relase.
SVN:trunk[3255]
2014-07-09 09:49:02 +00:00
Denis Flaven
f6d0bda737 Properly render the "CONCAT_WS" SQL expression used by the global search.
SVN:trunk[3254]
2014-07-09 09:47:21 +00:00
Denis Flaven
a64b299644 "Portal Users" are not allowed to use the REST/JSON webservices. This case is now properly handled with a specific message.
SVN:trunk[3253]
2014-07-08 10:47:50 +00:00
Denis Flaven
e022bf03db Oops: Protect against non-existing parameters... (occurs when configuring a list)
SVN:trunk[3252]
2014-07-08 10:01:47 +00:00
Denis Flaven
bab6ca7954 Protection against a non existing UserLDAP class (in case the LDAP module is not installed)
SVN:trunk[3251]
2014-07-07 17:02:40 +00:00
Romain Quetiez
bb70884041 Readme file ready for 2.0.3 (RC), planned for the 15th
SVN:trunk[3250]
2014-07-07 15:55:27 +00:00
Denis Flaven
58cb67ecd3 Protect against non-existing parameters... (occurs when configuring a list)
SVN:trunk[3249]
2014-07-07 10:25:52 +00:00
Denis Flaven
4920cc4aee Fix in the log of the setup: Properly compute the percentage of "useless" records in priv_change.
SVN:trunk[3248]
2014-07-04 15:30:36 +00:00
Denis Flaven
12c587896e #927 Detect IE7 (or compatibility mode) and warn the user that the application cannot be used with such browsers.
SVN:trunk[3247]
2014-07-04 15:22:00 +00:00
Denis Flaven
eb74288c85 Added an index on "item_org_id" to speed-up the setup and the silo on organizations.
SVN:trunk[3246]
2014-07-04 14:15:24 +00:00
Denis Flaven
922049e8f2 Provide feedback while loading asynchronous tabs.
SVN:trunk[3245]
2014-07-04 13:18:47 +00:00
Denis Flaven
d83edb803b Better handling of the user's configured display limit for displaying the CSV import history. Also provide feedback while reloading.
SVN:trunk[3244]
2014-07-04 13:18:07 +00:00
Denis Flaven
9f41c7e577 Automatic cleanup of the table 'priv_change' before upgrading.
SVN:trunk[3243]
2014-07-04 13:09:27 +00:00
Denis Flaven
1335a15c30 Fixed a regression introduced by [r3141]: display of objects templates was broken.
SVN:trunk[3242]
2014-07-03 16:51:50 +00:00
Romain Quetiez
d847272264 Reworked the asynchronous mechanism to ease its extension (added a "status", preset by the setup)
SVN:trunk[3241]
2014-07-03 14:31:54 +00:00
Denis Flaven
8054d7b17a #954 Stricter validation of mandatory tags/values during compilation.
SVN:trunk[3240]
2014-07-03 12:26:04 +00:00
Romain Quetiez
1ba2ed809c Added a warning when upgrading the application while a CRON is being executed on the same DB
SVN:trunk[3239]
2014-07-03 07:11:43 +00:00
Denis Flaven
b2feca5eb5 #944 Better initialization of the 'origin' field based on localized comments.
SVN:trunk[3238]
2014-07-02 09:32:05 +00:00
Denis Flaven
87ce86880e Added Jean-François Bilger to the "Thank you" section
SVN:trunk[3237]
2014-07-01 17:03:05 +00:00
Denis Flaven
d4de0d2ee3 Fixed a regression introduced by [3188], which affects the Audit results and also potentially the User rights. Many thanks to Jean-Francois Bilger for finding the issue (and the associated fix) !
SVN:trunk[3236]
2014-07-01 16:40:38 +00:00
Denis Flaven
730f522ab2 #944 Automatically create an index on the 'origin' field.
SVN:trunk[3235]
2014-07-01 15:44:24 +00:00
Denis Flaven
008cac25b4 #944 Speed-up the display of CSV import history. IF the feature is enabled, the CSV import history is:
1) Displayed asynchronously
2) Based on a specific 'origin' enum fields instead of parsing the userinfo comment

SVN:trunk[3234]
2014-07-01 14:09:18 +00:00
Denis Flaven
ede552968b Added the (missing) field status to the details of VirtualDevice, Hypervisor and Farm.
Fixed two French translations.

SVN:trunk[3233]
2014-07-01 09:15:54 +00:00
Denis Flaven
686848a0ae #953 Protect the background processing against corrupted (incomplete) objects.
SVN:trunk[3231]
2014-06-30 15:24:49 +00:00
Denis Flaven
61b5b5cc71 #906 Better way to handle the lock in order to prevent duplicates in the numbering of Tickets. Note that the iTopMutex now supports re-entrancy inside the same PHP page.
SVN:trunk[3230]
2014-06-27 13:45:48 +00:00
Denis Flaven
ec16c5f86f #940 "Close" and "Reopen" buttons were not working on IE (due to extra commas at the end of JS lists)
SVN:trunk[3228]
2014-06-27 11:02:52 +00:00
Denis Flaven
d602bb2190 #948 Finalization of the full text accelerators: two new parameters: 'skip' and 'enable_enlarge'. 'attributes' is now an optional parameter.
SVN:trunk[3227]
2014-06-26 14:27:26 +00:00
Denis Flaven
1b9d3d3304 Applied again the patch [r3025] to support an empty TO: header when using the PHP Mail transport.
SVN:trunk[3226]
2014-06-26 12:16:26 +00:00
Denis Flaven
02e5d57998 #949 Upgraded SwiftMailer to the latest version (5.2.1)
SVN:trunk[3225]
2014-06-26 12:07:35 +00:00
Denis Flaven
6236d96a65 #951 Properly display localized values for ENUMs in the data model view.
SVN:trunk[3224]
2014-06-25 16:51:26 +00:00
Denis Flaven
6473a8f245 #892 Refresh the "multi-select" when picking a value with the "hierarchical picker", inside a search form.
SVN:trunk[3223]
2014-06-25 14:37:13 +00:00
Denis Flaven
ab93eaefff #916 Hierarchical selector: clicking on the label now has the same effect as clicking on the radio button itself. (The label is no longer an hyperlink)
SVN:trunk[3222]
2014-06-24 15:56:24 +00:00
Denis Flaven
037803c033 Protect attachments against concurrent edition of a ticket.
SVN:trunk[3221]
2014-06-24 10:03:34 +00:00
Denis Flaven
06582cfe35 Oops, fixed a typo in: Add the ability to supply a default "from" email address for the "forgot password" feature, instead of using the same address as for the "to".
SVN:trunk[3219]
2014-06-18 15:57:25 +00:00
Denis Flaven
a72e15023f Proper handling of the re-entrence of the Lock/Unlock within the same PHP page. Previously the code was sometimes performing 2 Unlock() for each Lock().
SVN:trunk[3217]
2014-06-18 15:24:31 +00:00
Romain Quetiez
7ddd74a95e Fix to complete the commit [3193]: template abusively selected on the user portal
SVN:trunk[3216]
2014-06-17 13:10:41 +00:00
Denis Flaven
4fccf5c815 Add the ability to supply a default "from" email address for the "forgot password" feature, instead of using the same address as for the "to".
SVN:trunk[3213]
2014-06-16 15:14:40 +00:00
Romain Quetiez
a6d984e23f Removed a debug trace introduced with the file drag&drop capability
SVN:trunk[3212]
2014-06-13 09:48:31 +00:00
Denis Flaven
7bf3f97a72 Release of the 2.0.3-beta, adjusting the date!
SVN:trunk[3211]
2014-06-13 09:29:23 +00:00
Denis Flaven
e78743d309 Fixed a regression introduced by #930: a field can depend on a hidden fields, but this has no visible effect.
SVN:trunk[3210]
2014-06-13 09:10:17 +00:00
Denis Flaven
6b86ac5090 Protect the optimization in case the object's friendlyname is requested.
SVN:trunk[3209]
2014-06-12 14:45:33 +00:00
Romain Quetiez
14c78cb543 Fixed a regression on the global search, introduced in the commit [3188]
SVN:trunk[3208]
2014-06-12 09:27:09 +00:00
Denis Flaven
5007e6ffc3 Updating the readme and the licence with the drag and drop feature.
SVN:trunk[3207]
2014-06-11 07:43:15 +00:00
Denis Flaven
1d4784b0c7 Allow uploading files as Tickets' attachments via drag and drop (on "modern" browsers supporting HTML5 drag and drop)
SVN:trunk[3206]
2014-06-10 15:05:02 +00:00
Denis Flaven
d497020733 Oups, version number back to: 1.0.5 = beta 6, with the correct collection of VLANs as DistributedVirtualPortgroups
SVN:trunk[3205]
2014-06-10 15:01:45 +00:00
Romain Quetiez
98afb2dd49 Datamodel version changed to 2.0.3
SVN:trunk[3204]
2014-06-10 14:14:27 +00:00
Romain Quetiez
f89792e171 Demo mode: the profiles must be in read-only mode (it is not enough to have the users in read-only)
SVN:trunk[3203]
2014-06-10 11:50:20 +00:00
Romain Quetiez
8141f8c619 Fixed the readme file for 2.0.3 Beta
SVN:trunk[3202]
2014-06-06 15:53:14 +00:00
Denis Flaven
2009a10204 Preparing the readme for iTop 2.0.3-beta.
SVN:trunk[3201]
2014-06-06 14:18:48 +00:00
Romain Quetiez
d2961c585e #926 JSON/REST Delete: nasty report
SVN:trunk[3200]
2014-06-06 09:50:57 +00:00
Denis Flaven
9b5fc043cb #943: Fix for supporting drop-down lists/auto-completes based on a parametrized query in the portal.
SVN:trunk[3199]
2014-06-06 09:43:20 +00:00
Romain Quetiez
67ef671632 #865 CSV import : mapping several columns to the same fields gives an error. Fix: the wizard does not allow the user to move forward.
SVN:trunk[3198]
2014-06-05 14:42:53 +00:00
Romain Quetiez
d07ca49e53 #636 and #861 Set the focus on User Name in iTop Login Form
SVN:trunk[3197]
2014-06-05 09:48:59 +00:00
Denis Flaven
44aff84dfa #934: Support the <display_style> option for ExternalKeys.
SVN:trunk[3196]
2014-06-05 09:19:15 +00:00
Romain Quetiez
c9ffdb9342 Cleanup: the model.x file will be rebuilt from the xml file anyway!!!
SVN:trunk[3195]
2014-06-04 14:51:36 +00:00
Romain Quetiez
65343a485f Completed the portuguese translation (Brazil), provided in december... provided some time ago (by whom?) - modules updated: attachments, change, incident, request and request/ITIL, service for providers
SVN:trunk[3194]
2014-06-04 14:29:22 +00:00
Romain Quetiez
74cda2831f Portal + templates: Bug fix = when the user selects a template, then go back to select a service for which no template applies, he still gets the tempate fields in the final form.
SVN:trunk[3193]
2014-06-04 13:37:58 +00:00
Romain Quetiez
c4632cda1a Added a helper function to get an icon stored as an ormDocument: ormDocument::GetDownloadURL
SVN:trunk[3191]
2014-06-04 13:10:23 +00:00
Romain Quetiez
d7ba4166e5 #919 Circular references between tickets (parent/child). Protect the framework against infinite recursions on cascaded updates (done at the DBUpdate level).
SVN:trunk[3190]
2014-06-03 18:05:31 +00:00
Romain Quetiez
ee3795ef6d Full text search shortcuts: allow the use of class names containing underscores and numbers (e.g. Processus métier: écarissage)
SVN:trunk[3189]
2014-06-03 17:57:53 +00:00
Romain Quetiez
e8a7695353 #731 Full text search requires a string of at least three characters (configurable: full_text_needle_min)
SVN:trunk[3188]
2014-06-03 14:42:17 +00:00
Romain Quetiez
e4bb0acd94 #936 Tune the default (i.e. implicit) tracking level on link sets (and disable tracking on 1-N links, for fresh installations)
SVN:trunk[3187]
2014-06-03 13:36:35 +00:00
Denis Flaven
1572d9da5a #930 AttributeExternalFields displayed in a form are automatically refreshed when their "parent" field is modified...
SVN:trunk[3186]
2014-06-02 16:05:20 +00:00
Romain Quetiez
2d0ca37f27 #854 Flag Is null allowed not working on attributes Date and DateTime + the default value is now operant
SVN:trunk[3185]
2014-06-02 14:35:28 +00:00
Romain Quetiez
df09182878 Oooops... completes the commit 3175 (#929 Speed up the full text search)
SVN:trunk[3184]
2014-06-02 14:22:14 +00:00
Denis Flaven
20bb9a62bb #935 Better support of CheckToWrite() in object's transitions, improved by checking the data sooner for a consistent workflow.
SVN:trunk[3183]
2014-06-02 13:16:37 +00:00
Romain Quetiez
a6f5436d07 #873 Allow the character % in the path of an URL (requires the edition of the config file when upgrading)
SVN:trunk[3182]
2014-06-02 10:23:06 +00:00
Denis Flaven
53e58d5887 #872 Support notifications for the creation of a new user. Also fix the translation of the "Additional values" in ValueSetEnumClasses.
SVN:trunk[3181]
2014-05-30 17:11:37 +00:00
Denis Flaven
ef1178c78d #935 Better support of CheckToWrite() in object's transitions
SVN:trunk[3180]
2014-05-30 16:43:14 +00:00
Denis Flaven
5456d9d20a #933 Search form for Query Phrasebook items. If you implement your own menus (equivalent to itop-welcome-itil module), make sure that you update the menu definition to show the search form at the top.
SVN:trunk[3179]
2014-05-30 07:42:17 +00:00
Denis Flaven
7c8ff071d2 #931 Management of n:n links can be broken in case of insufficient user rights. Side effect: attribute_linkedset with the flag OPT_ATT_HIDDEN are now completely hidden (the tab is not displayed at all).
SVN:trunk[3178]
2014-05-28 16:37:25 +00:00
Romain Quetiez
e4010b4b13 #886 Delete change history so that if an ID is reused the history starts from scratch (and cleanup most of the data as soon as the object is deleted)
SVN:trunk[3177]
2014-05-28 16:00:24 +00:00
Denis Flaven
dbb9558b45 #862: Popup menu misplaced when the window scrolls (e.g. when displaying large lists of results)
SVN:trunk[3176]
2014-05-28 15:22:57 +00:00
Romain Quetiez
8f83970239 #929 Speed up the full text search (mostly from the end user perspective, requires a custom configuration)
SVN:trunk[3175]
2014-05-26 15:42:47 +00:00
Romain Quetiez
102b6c248b #928 Setup crashing if async_retries is configured
SVN:trunk[3174]
2014-05-26 14:45:07 +00:00
Denis Flaven
946b4212d3 Fix of a regression introduced by [3146]: due to formatting rules, empty friendlynames may not look like empty strings!
SVN:trunk[3172]
2014-05-23 15:49:30 +00:00
Denis Flaven
e2e6861b03 Properly handle external and basic authentication methods for REST web services.
SVN:trunk[3170]
2014-05-23 13:53:20 +00:00
Denis Flaven
7b7e69a890 Properly optimize the columns to load, when subitems are requested.
SVN:trunk[3168]
2014-05-23 13:08:31 +00:00
Romain Quetiez
92b2131d3b #483 Added placeholders for the notifications: html(caselog), head_html(caselog), html(linkset). The HTML can be customized. Fixes the issue about lines being wrapped in a curious way (root cause: swift mailer).
SVN:trunk[3167]
2014-05-23 12:32:08 +00:00
Romain Quetiez
43130aef71 Allow the use of any character into the help text on an attribute (usefull to explain a constraint implemented as a regular expression for instance.) Reminder: the text is given as a dictionary entry named like "Class:<class>/Attribute:<attcode>?"
SVN:trunk[3166]
2014-05-23 08:45:36 +00:00
Romain Quetiez
ba4e5ec786 #925 Completes the commit [3163], the link sets were still not correctly handled
SVN:trunk[3164]
2014-05-22 13:23:47 +00:00
Romain Quetiez
3e79dad435 #925 REST/JSON: Added an option to output all the fields of the object found (not only the fields of the queried class)
SVN:trunk[3163]
2014-05-20 08:15:53 +00:00
Romain Quetiez
b0a84f96f1 #897 Rest Services: improved the error reporting when an external key is specified with a final class that is not a subclass of the class of the external key
SVN:trunk[3162]
2014-05-19 15:02:42 +00:00
Romain Quetiez
85fa578f2a #876 Upgrade finishes with error "Cannot reload object id = -1" (root cause: DB in read-only mode, see config/access_mode)
SVN:trunk[3161]
2014-05-19 14:23:52 +00:00
Romain Quetiez
364bce90e4 #859 About box: also list the modules installed from the extensions folder
SVN:trunk[3160]
2014-05-19 11:43:45 +00:00
Romain Quetiez
fcfc1e7307 #875 Could not use OQL queries with a double quote in the condition
SVN:trunk[3159]
2014-05-19 08:28:39 +00:00
Romain Quetiez
85cb04a3f3 Fixed a compiler error message (wrong syntax when using a PHP class to implement the class methods)
SVN:trunk[3158]
2014-05-19 07:28:26 +00:00
Denis Flaven
ec77e58276 Limit the display of the status to the latest 100 runs of the synchro data source.
SVN:trunk[3157]
2014-05-16 05:02:38 +00:00
Denis Flaven
196fba7d81 Implement the iDisplay interface on any class derived from DBObject, but also limit the possible actions on such objects (disable edition)
SVN:trunk[3156]
2014-05-16 05:01:45 +00:00
Denis Flaven
317344da2e Proper output of boolean values in JSON.
SVN:trunk[3155]
2014-05-16 04:58:45 +00:00
Romain Quetiez
cd37a78800 Fixed a regression due to the optimization of the memory usage of DBObjectSet [3142]
SVN:trunk[3154]
2014-05-15 13:54:45 +00:00
Denis Flaven
9ed5ceb11e Fix for a regression introduced by [3149]
SVN:trunk[3153]
2014-05-14 13:47:40 +00:00
Denis Flaven
f47327fdd4 Second fix for a regression due to revisions 3141/3150
SVN:trunk[3152]
2014-05-14 13:24:19 +00:00
Denis Flaven
5ec37c8060 Fix for a regression due to revisions 3141/3150
SVN:trunk[3151]
2014-05-14 13:16:41 +00:00
Denis Flaven
bb65153351 Fixed regression introduced by [3141]: Code cleanup to implement the tabs handling (inside web pages) in one place.
SVN:trunk[3150]
2014-05-07 13:14:19 +00:00
Denis Flaven
389b97dc50 Two experimental perf. enhancements:
- maintain list the attributes (potentially) modified to speed-up ListChanges() by avoiding a systematic comparison between the content of linkedsets.
- cache the list of SynchroDataSources and use this in InSyncScope() to avoid searching in the SynchroReplicas when it's not needed...

Depending on the configuration, these optimizations may speed-up the CSV import by up to 40% !!

SVN:trunk[3149]
2014-05-06 15:45:04 +00:00
Denis Flaven
31e30810f6 Experimental perf. enhancement: cache the foreign keys to use when importing object to avoid searching for the same object several times during a given import. Seem to speed up the imports by 7 to 10%.
SVN:trunk[3148]
2014-05-06 15:39:01 +00:00
Denis Flaven
4bffe7aec9 Oops, removing a debug trace...
SVN:trunk[3147]
2014-05-06 15:34:26 +00:00
Denis Flaven
fafa442b08 Avoid using an attribute which may cause a Reload()... seems useless anyway.
SVN:trunk[3146]
2014-05-06 15:25:00 +00:00
Denis Flaven
b095c6b1a3 #909: faster display for the "details" of an object:
- object's history is only loaded when the "History" tab is clicked
- by default the history display is truncated to the 'max_history_length' (= 50) latest modifications

SVN:trunk[3145]
2014-05-06 15:15:57 +00:00
Denis Flaven
d950422912 #867 (and #907 as a dup') De-harcode set_time_limit (per loop) in lengthy operations. Default value is 30 seconds (per loop), configurable via the new parameter "max_execution_time_per_loop", instead of 5 seconds previously.
SVN:trunk[3144]
2014-05-06 14:09:55 +00:00
Denis Flaven
b504830f45 CSV export (from the toolkit menu) now displays an asynchronous page, to better cope with a huge number of objects (> 10000)
SVN:trunk[3143]
2014-05-06 13:54:45 +00:00
Denis Flaven
4dabb566a8 - Memory optimization: no longer store all DBObjects in memory while browsng through a Set, but pull them one by one from the MySQL client buffer as needed.
- Also renamed Merge to Append since it's really what it does (seems to be used only in the tests)

SVN:trunk[3142]
2014-05-06 13:46:45 +00:00
Denis Flaven
7459ec4844 Code cleanup to implement the tabs handling (inside web pages) in one place. Added the ability to provide asynchronously loaded tabs (content must come from the same server).
SVN:trunk[3141]
2014-05-06 13:42:18 +00:00
Denis Flaven
678f982024 #923: prevent XSS injection in forgot password page.
SVN:trunk[3139]
2014-05-06 08:26:54 +00:00
Denis Flaven
d628c4f670 Use the object oriented verison of the MySQLi API which seems free of memory leaks (compared to the procedural version of the same API).
SVN:trunk[3138]
2014-05-02 14:00:42 +00:00
Denis Flaven
e1336d7ebc Run Query enhancements
- Properly catch *all* exceptions and redisplay the entered OQL statement every time
- Post the form to force its refresh (i.e. running the query again) even if the query did not change

SVN:trunk[3137]
2014-05-02 13:30:08 +00:00
Romain Quetiez
01496f9595 #918 TTO/TTR status "passed" gets reset when the stop watch is stopped (using the status "triggered" instead)
SVN:trunk[3130]
2014-04-24 08:53:17 +00:00
Denis Flaven
a60d60bfab Better handling of the default choices in case of upgrade (for some specific configurations of the installation wizard).
SVN:trunk[3127]
2014-04-15 13:01:06 +00:00
Romain Quetiez
31cb0065b5 Integrated the portal with the module to dispatch incidents or user requests (the previous commit introduced too many changes, I did rollback unnecessary stuff)
SVN:trunk[3125]
2014-04-11 14:53:36 +00:00
Romain Quetiez
4dbc5d97b8 Integrated the portal with the module to dispatch incidents or user requests
SVN:trunk[3124]
2014-04-11 14:42:57 +00:00
Romain Quetiez
4800da1653 #913 Error when searching for child requests and no organization is specified. Still, I could not figure out WHY IT WAS WORKING WHEN AN ORG IS SELECTED as a search filter!
SVN:trunk[3123]
2014-04-09 14:00:09 +00:00
Denis Flaven
8f25fb8e64 #878: Missing sscrollbar in "linkset-direct" edition popup dialog
#881: Paginated list in popup dialog is broken
- Missing scrollbar in the popup when using the [+] button

SVN:trunk[3121]
2014-04-01 13:09:02 +00:00
Denis Flaven
b43884a760 Object's edition: keep track of what was typed in the case log fields when reloading the form (for example with a different "initial state")
SVN:trunk[3119]
2014-04-01 12:06:35 +00:00
Denis Flaven
caef02720c Protect Bulk Modify against XSS injection!
SVN:trunk[3117]
2014-04-01 10:12:11 +00:00
Denis Flaven
ce9806b01c Allow re-entrance in the same named mutex within the same PHP page.
SVN:trunk[3115]
2014-04-01 09:50:21 +00:00
Romain Quetiez
7a193d1c24 Fixed issue with 1.x datamodels: dashlets of type "badge" not working (preventing from editing an existing dashboard), since 2.0.2
SVN:trunk[3113]
2014-04-01 09:14:06 +00:00
Romain Quetiez
31ea53435e Aligned the authentication module with the one of 2.x, to enable the feature "Forgot password" for legacy data models
SVN:trunk[3111]
2014-04-01 08:31:08 +00:00
Denis Flaven
dcb48d0f35 Bug fix : missing semicolons were causing an error with IE9.
SVN:trunk[3107]
2014-03-26 14:58:32 +00:00
Romain Quetiez
40aa78bb3d Finalized the French transalation for some types of "Triggers"
SVN:trunk[3106]
2014-03-24 10:21:09 +00:00
Romain Quetiez
82e9e42939 #901 Added the attribute "filter" to the triggers
SVN:trunk[3104]
2014-03-21 10:52:20 +00:00
Romain Quetiez
1ecec1dd6d #905 The toolkit menu was visible in the portal for Administrators (but it was not usable). It is now hidden in any case.
SVN:trunk[3103]
2014-03-20 10:27:13 +00:00
Denis Flaven
8dc5b05ac4 #870: when a user deletes all her/his shortcuts at once, this was deleting all the shortcuts for all users.
SVN:trunk[3099]
2014-03-12 14:05:37 +00:00
Denis Flaven
3b257eeb3a Templates processing aligned with "templates-base" 2.1.1: allow template fields with the same name the attribute code of the curent object.
SVN:trunk[3097]
2014-03-11 14:03:14 +00:00
Denis Flaven
2574a0c8a4 #887 short term fix for preventing ToArgs to alter the content of an object...
SVN:trunk[3095]
2014-03-04 13:58:28 +00:00
Romain Quetiez
404f6772fd #896 XSS injection on the portal (any search form)
SVN:trunk[3093]
2014-03-03 14:46:48 +00:00
Denis Flaven
d6dbe0fce7 Trac #890: Dispatch the defines in the proper modules to make sure that the portal works with all possible combinations of tickets.
SVN:trunk[3092]
2014-03-03 13:48:56 +00:00
Romain Quetiez
8e26ca763b Internal: modified the prototype of GetDelta to add several attributes on the root node
SVN:trunk[3090]
2014-02-26 11:24:10 +00:00
Denis Flaven
b4bc3ad716 Trac #891: better error reporting when either the parameter auth_user or auth_pwd are missing.
SVN:trunk[3089]
2014-02-25 13:37:01 +00:00
Denis Flaven
80fac28106 Trac #890: Dispatch the defines in the proper modules to make sure that the portal works with all possible combinations of tickets.
SVN:trunk[3088]
2014-02-25 13:17:25 +00:00
Romain Quetiez
5f11c97aef #888 Security on the portal incompatible with customizations (regression introduced in 2.0.2), now requires to define PORTAL_USERREQUEST_DISPLAY_QUERY and PORTAL_USERREQUEST_DISPLAY_POWERUSER_QUERY
SVN:trunk[3086]
2014-02-24 15:52:04 +00:00
Denis Flaven
d36a03bfc3 Make the Basic Authentication (login_mode=basic) work with non-ASCII characters (in the username as well as in the password), though this may depend on the browser...
SVN:trunk[3084]
2014-02-19 17:34:53 +00:00
Denis Flaven
8e0c57fce0 Add a new flag "debug" (false by default) to turn off the debug traces of the module since the traces contain potentially sensitive information in clear text.
SVN:trunk[3083]
2014-02-14 17:51:50 +00:00
Romain Quetiez
cb4c2a8e84 Internal: Manage nested subforms in property sheet edition mode
SVN:trunk[3082]
2014-02-12 14:14:01 +00:00
Denis Flaven
f993f07751 Enhanced API to support icon upload...
SVN:trunk[3081]
2014-02-10 15:41:32 +00:00
Romain Quetiez
0167a66973 Demo mode: disable the pin button on the left pane (and keeps it open and resizable)
SVN:trunk[3080]
2014-02-10 13:21:21 +00:00
Romain Quetiez
b7d8953ecb Fix for Plugins: fixed a regression introduced in 3072 (dashboards not editable at all!!!)
SVN:trunk[3079]
2014-02-06 16:27:22 +00:00
Romain Quetiez
cba75527b3 #877 REST/JSON More flexibility on case log updates (in particular, it is now possible to write the entire case log), remains compatible with the previous API
SVN:trunk[3078]
2014-02-06 11:40:26 +00:00
Denis Flaven
bc0f48721b Bug fix: the JSON value for an enum should be the raw value, not its translated label.
SVN:trunk[3077]
2014-02-06 09:22:35 +00:00
Denis Flaven
a6693d9535 Protect the initialization with a try ... catch, in order to protect that rest of the page in case of trouble.
SVN:trunk[3074]
2014-02-05 17:08:33 +00:00
Romain Quetiez
6f2c404415 Fix for Plugins: if a page uses set_base then JS popup menu items were reloading the page. Still, set_base should not be used!
SVN:trunk[3072]
2014-02-04 16:04:50 +00:00
Romain Quetiez
b00aae2536 Enabled KPI tracing for the export page... fixed regression on the setup page (rewrote the change in a much more defensive way)
SVN:trunk[3071]
2014-02-04 14:20:20 +00:00
Romain Quetiez
6334370ef0 Optimization: map the extended attribute code to the corresponding external field when this if possible (ex: org_id->name to org_name); this reduces the number of queries, in particular when using the "export CSV" menu on a list.
SVN:trunk[3069]
2014-02-03 14:35:05 +00:00
Romain Quetiez
868748efb3 Enabled KPI tracing for the export page
SVN:trunk[3067]
2014-02-03 14:26:19 +00:00
Romain Quetiez
c67e7e18e6 Optimize the queries for the export page
SVN:trunk[3066]
2014-02-03 14:23:55 +00:00
Romain Quetiez
16f1fd56ec Resetting the stop watch...do clean the first start date when it is not running!
SVN:trunk[3065]
2014-02-03 14:22:37 +00:00
Romain Quetiez
1cab84e793 Allow to reset a running stop watch (without stopping it!)
SVN:trunk[3063]
2014-02-03 11:56:22 +00:00
Denis Flaven
991fe9ccc1 Compatibility with APCu (For PHP 5.5+), since it slightly different from APC.
SVN:trunk[3061]
2014-01-22 15:18:48 +00:00
Denis Flaven
0a53f8ec3f Preserve "hidden" template fields.
SVN:trunk[3060]
2014-01-22 14:09:53 +00:00
Denis Flaven
cd7af7a2ce Dictionary string for the portal should not depend on a module
SVN:trunk[3059]
2014-01-22 14:08:34 +00:00
Denis Flaven
319b3b81ce - Put back support of templates
- Make sure that unwanted parameters cannot be set when creating the ticket

SVN:trunk[3058]
2014-01-21 10:47:45 +00:00
Denis Flaven
4235eae9b3 #871: eMail validation pattern was too strict: now fully configurable (globally and per attribute).
SVN:trunk[3056]
2014-01-17 09:39:19 +00:00
Romain Quetiez
32ec19e09d #869 REST JSON was not outputing case log attributes (implemented in a structured way)
SVN:trunk[3054]
2014-01-15 11:04:25 +00:00
Denis Flaven
63ea142168 Record the very same installation time for all modules.
SVN:trunk[3052]
2014-01-03 14:00:44 +00:00
Denis Flaven
23ec4faa65 Test commit: bumped the year to 2014.
SVN:trunk[3051]
2014-01-02 18:02:39 +00:00
Denis Flaven
da36fc673e Test commit: just bumping the copyright year
SVN:trunk[3050]
2014-01-02 17:59:39 +00:00
Romain Quetiez
3b65f33325 Internal: correctly quote XPath literals within GetNodeById
SVN:trunk[3049]
2013-12-17 14:52:20 +00:00
Romain Quetiez
e980b051b1 Asynchronous emails: added a retry mechanism useful in case your SMTP server restricts the number of emails that can be sent over a period of time (usage: broadcasting a newsletter). The mechanism is not specific to sending email as it is implemented at the AsyncTask level.
SVN:trunk[3047]
2013-12-13 10:35:37 +00:00
Denis Flaven
c84a22c503 #856: allow asynchronous emails to have an empty 'to' recipient... (not used anyway)
SVN:trunk[3043]
2013-12-12 18:02:32 +00:00
Denis Flaven
5c2578169e Add "finalclass" as a reconciliation key on all abstract classes derived from FunctionalCI
SVN:trunk[3040]
2013-12-11 10:08:23 +00:00
Romain Quetiez
645731a76d Integrated an update of the portuguese (brazil) localization, made by Marco Tulio
SVN:trunk[3039]
2013-12-11 09:38:15 +00:00
Denis Flaven
3de2d654a0 Protection against attemp to delete a non-existing node in the XML...
SVN:trunk[3038]
2013-12-10 16:43:22 +00:00
Romain Quetiez
934e500253 Setup: fixed issue when upgrading a DB (no install dir specified, thus no config file) and requesting a backup: the backup is created without the config file in it.
(Updated the readme for the upcoming release!)

SVN:trunk[3037]
2013-12-10 15:47:33 +00:00
Romain Quetiez
cfd2a7baff Readme file updated for the (soon) upcoming release 2.0.2
SVN:trunk[3036]
2013-12-10 15:01:34 +00:00
Romain Quetiez
1867195c25 Portal: Removed the public log for the user request creation form (still available on the ticket details). That was already done in change [2828] for ITIL flavour.
SVN:trunk[3035]
2013-12-10 14:58:22 +00:00
Romain Quetiez
d4bcb9dff8 Regression of 2.0.2 beta: Configure this list + discard all columns: the list cannot be loaded anymore (if saved)
SVN:trunk[3034]
2013-12-10 13:48:32 +00:00
Romain Quetiez
4172cb2023 Readme file updated for the (soon) upcoming release 2.0.2
SVN:trunk[3033]
2013-12-10 12:48:10 +00:00
Denis Flaven
ebff827013 #805 (again): proper fix to avoid blocking when creating a ticket with DBInsert() (instead of DBInsertNoReload !!)
SVN:trunk[3032]
2013-12-10 11:07:24 +00:00
Denis Flaven
1afcf46970 #805: fix the issue when creating tickets from the SOAP web service.
SVN:trunk[3031]
2013-12-10 10:16:40 +00:00
Romain Quetiez
2e37ccc4c2 #770 and #853: When a list is configured directly from the table (NOT from the Dialog box), then these settings are not kept neither when creating a shortcut nor when opening the "configure this list" dialog
SVN:trunk[3030]
2013-12-09 20:38:05 +00:00
Romain Quetiez
3b188524ca #770 Complete the fix implemented in [2990], the configuration is kept even if not saved for the list
SVN:trunk[3029]
2013-12-09 15:55:11 +00:00
Denis Flaven
bd1096b0fc #756: allow incidents and user request to be linked to 'closed' problems.
SVN:trunk[3028]
2013-12-09 12:57:55 +00:00
Denis Flaven
d42443697c Security enhancements:
- ensure that a user can ony see the details of the ticket she/he is allowed to see, even if the id is typed manually
- add a define'd filter to filter the drop-down lists of the search form for searching closed tickets.

SVN:trunk[3027]
2013-12-09 11:39:25 +00:00
Romain Quetiez
8509237084 #851 Ampersand (aka '&') not welcomed in the setup wizard
SVN:trunk[3026]
2013-12-09 10:52:21 +00:00
Romain Quetiez
f16d1ee1e4 PHP Mail transport to allow 100% of recipients in BCC (proposed on github)
SVN:trunk[3025]
2013-12-05 12:06:30 +00:00
Romain Quetiez
5672bee85f Regression introduced with the capability to disable mandatory ext keys (defaults to true)
SVN:trunk[3024]
2013-12-04 16:15:26 +00:00
Denis Flaven
4d6ddb8586 Fixes to the packaging after a first test on CentOS
SVN:trunk[3023]
2013-12-03 15:21:22 +00:00
Denis Flaven
a71b3bc231 Protects the setup in case of missing PHP-JSON module
SVN:trunk[3022]
2013-12-03 11:47:42 +00:00
Romain Quetiez
723d51a871 Readme file updated for the upcoming release
SVN:trunk[3021]
2013-12-03 11:07:59 +00:00
Denis Flaven
4e1c3f321f First step toward a generic Linux packaging supporting both DEBs and RPMs...
SVN:trunk[3020]
2013-12-03 11:04:33 +00:00
Denis Flaven
2b58bca313 Use jQuery.inArray() instead of array.indexOf() to be compatible with IE8
SVN:trunk[3019]
2013-12-03 10:50:38 +00:00
Denis Flaven
9b1d383848 Spanish translation contributed by Miguel Turrubiates
SVN:trunk[3018]
2013-12-03 10:22:30 +00:00
Romain Quetiez
96c1ec42ed Internal: API to build the SQL statements to (re)-create an object
SVN:trunk[3017]
2013-12-03 09:59:42 +00:00
Romain Quetiez
7cb2fb9b02 Internal: new setting to tweak the data model by allowing null for all external keys (use with care!)
SVN:trunk[3016]
2013-12-03 09:58:29 +00:00
Romain Quetiez
6f90d626fc Code refactoring: MakeSelectFilter
SVN:trunk[3015]
2013-12-03 09:56:46 +00:00
Romain Quetiez
62302f9138 Reverted the change made in revision 2999: revision 3000 does protect against the annoying notice... and everything seems working fine... still to be tested with PH >=5.4
SVN:trunk[3014]
2013-12-03 08:42:48 +00:00
Romain Quetiez
278cb653db Fixed regression in the Notifications page (leading to a conflict between the two tabs)
SVN:trunk[3013]
2013-12-03 08:33:54 +00:00
Romain Quetiez
9f9baf9caa Finalized the about box
SVN:trunk[3012]
2013-11-29 16:18:01 +00:00
Denis Flaven
09ebce2587 Transparent background
SVN:trunk[3011]
2013-11-29 16:08:39 +00:00
Denis Flaven
3c8cf0e8fb Bug fix: duplicate ID was used for the VLAN badge
SVN:trunk[3010]
2013-11-29 15:13:52 +00:00
Romain Quetiez
5542cfd79e Module refactoring: Configuration management must not require the installation of a Service Management module
SVN:trunk[3009]
2013-11-29 14:57:34 +00:00
Denis Flaven
60e7c22ab4 #838 following on the bug fix: more standard naming for the parameters of the query
SVN:trunk[3008]
2013-11-29 11:38:51 +00:00
Denis Flaven
92502a7a88 bug fix: Allow Support Agents to perform "wait for approval" on a UserRequest ticket.
SVN:trunk[3007]
2013-11-29 11:28:39 +00:00
Romain Quetiez
47c65b161d Fixed bug with the brand new about box
SVN:trunk[3006]
2013-11-29 11:23:51 +00:00
Romain Quetiez
5f98c0dcab About box - alpha version
SVN:trunk[3005]
2013-11-29 10:50:53 +00:00
Denis Flaven
9e4b25e833 #849: fix for the special case of loading class tags into a hierarchy
SVN:trunk[3004]
2013-11-28 17:15:09 +00:00
Denis Flaven
4b095738d5 #849: fix for the special case of loading class tags into a hierarchy
SVN:trunk[3003]
2013-11-28 16:28:01 +00:00
Romain Quetiez
fa615638d9 Defensive programming: protected against the Notice "array to string conversion" that appears in PHP 5.4 (thus not on every systems)
SVN:trunk[3002]
2013-11-28 09:55:39 +00:00
Romain Quetiez
a24b4437aa #816 Completed the fix (still a Notice)
SVN:trunk[3001]
2013-11-28 09:52:33 +00:00
Romain Quetiez
9f95d951d4 #825 and #830: removed annoying Notices (array to string conversion)
SVN:trunk[3000]
2013-11-28 09:44:21 +00:00
Romain Quetiez
7400bd7dca #816 Suppresses a Notice for PHP >= 5.4, and potentially fixes a bug (no idea on how to produce it)
SVN:trunk[2999]
2013-11-27 13:29:32 +00:00
Romain Quetiez
258b4be167 #830 (continuation: suppressed the notice "array to string conversion"
SVN:trunk[2998]
2013-11-27 09:20:16 +00:00
Denis Flaven
4a849ee4db #764: Saving settings as "Default for all lists" now works as expected
SVN:trunk[2997]
2013-11-27 08:51:51 +00:00
Erwan Taloc
18664c8151 Fix bug #757
SVN:trunk[2996]
2013-11-26 22:00:00 +00:00
Erwan Taloc
85472fe67a Fix bug #837 Remove [+] button on external keys where it's not meaningful
+ button had been removed for all ExternalKey having a complex filter defined

SVN:trunk[2995]
2013-11-26 21:45:54 +00:00
Erwan Taloc
e1087d3f87 Fix bug #835 Remove dependency between ticket and Delivery model
SVN:trunk[2994]
2013-11-26 21:19:11 +00:00
Erwan Taloc
e789c6baec Fix enhancement request #834 Add IP for Virtual Machine
SVN:trunk[2993]
2013-11-26 21:06:56 +00:00
Erwan Taloc
817cc0476a Fix bug #824 Change ranking in CI Overview dashboard
SVN:trunk[2992]
2013-11-26 20:55:59 +00:00
Erwan Taloc
ea5908ac41 Fix bug #836 Adding linkset "applicationsolution_list" in details list
SVN:trunk[2991]
2013-11-26 20:51:48 +00:00
Romain Quetiez
f5d42b95b8 Code cleanup
SVN:trunk[2990]
2013-11-26 15:59:22 +00:00
Romain Quetiez
d7093a9a6f #770 Preserve list configuration (if dedicated) when creating a shortcut
SVN:trunk[2989]
2013-11-26 15:58:05 +00:00
Denis Flaven
7636b987b1 #823: proper figures for the CSV import "confirmation" pie chart.
SVN:trunk[2988]
2013-11-26 14:28:22 +00:00
Romain Quetiez
55f1763b60 #830 ... completes the change 2980 (that accompanied changes 2921 and 2948, for an optimization on bulk operations)
SVN:trunk[2987]
2013-11-26 14:27:24 +00:00
Denis Flaven
87e33c72b5 #775: preserve the open/closed status of the search form during auto-reload... by not reloading the form at all!
SVN:trunk[2986]
2013-11-26 14:19:13 +00:00
Denis Flaven
99695a0fc1 #841: properly select the current tab
SVN:trunk[2985]
2013-11-26 13:54:17 +00:00
Denis Flaven
0aa0de9f1c Make sure that we attach ONLY the elements that are visible in the form when using the "select all" mode for managing 1:n links.
SVN:trunk[2984]
2013-11-26 11:16:11 +00:00
Denis Flaven
ebe89b0af7 #839: Managing n:1 links asked to leave the page (under Chrome!)
SVN:trunk[2983]
2013-11-26 10:39:56 +00:00
Denis Flaven
74f895b5f4 #838: the OQL query for getting the SLTs is now a constant defined in the XML
SVN:trunk[2982]
2013-11-26 10:03:53 +00:00
Romain Quetiez
9bc5406abb #842 Internal: recode the notifications page to allow several types of actions
SVN:trunk[2981]
2013-11-26 09:39:34 +00:00
Romain Quetiez
dd1cf43d41 #830 Regression introduced in the beta. Related to the management of query arguments
SVN:trunk[2980]
2013-11-25 15:10:09 +00:00
Romain Quetiez
b62b9caaf2 #829 Corrupted pwd when attempting to create an account without any profile
SVN:trunk[2979]
2013-11-25 09:01:35 +00:00
Romain Quetiez
36149df584 Extensibility: make sure that checks implemented in an overload of CheckToWrite will be seen when parent::CheckToWrite is invoked at the end of the overload
SVN:trunk[2978]
2013-11-21 16:30:58 +00:00
Romain Quetiez
e48716753d Optimizations: when displaying a "short" list, made of objects having the same status, three additional queries were made (now just one Group By query is made)
SVN:trunk[2977]
2013-11-21 16:15:37 +00:00
Romain Quetiez
8c702a42e9 #827 Default language given at setup not taken into account (login page always in english)
SVN:trunk[2976]
2013-11-21 15:33:09 +00:00
Romain Quetiez
494e559748 Improved the KPI logging: setting log_kpi_duration to 2 will enable blaming of the FIRST caller (callstack entirely shown in the produced report)
SVN:trunk[2975]
2013-11-20 17:03:04 +00:00
Romain Quetiez
a1801e53a2 Got rid of an unnecessary query (responsible for 8% of the time spent in a standard iTop page!!!)
SVN:trunk[2974]
2013-11-20 16:16:19 +00:00
Romain Quetiez
f856859f83 #832 Typos in German (tickets overview)
SVN:trunk[2973]
2013-11-19 13:16:10 +00:00
Denis Flaven
7ebce0a841 Improved version of DoPostRequest which optionally uses cURL to workaround PHP/OpenSSL bugs !
SVN:trunk[2972]
2013-11-07 15:03:01 +00:00
Romain Quetiez
3f50d3ea59 Helper for HTTP POSTs: can return the headers if requested
SVN:trunk[2971]
2013-11-06 15:29:15 +00:00
Denis Flaven
898c235c0d Fix for the validation of the forms in the portal. The (lack of) localization was breaking it!
SVN:trunk[2970]
2013-10-29 16:26:46 +00:00
Romain Quetiez
85e261a5fa Added user rights for VLAN and related classes
SVN:trunk[2969]
2013-10-29 16:01:22 +00:00
Romain Quetiez
d912e7f4fb #804 (Continued) Fix for GetHilightClass (previous revision: 2952) was not sufficient + applied the same fix for GetIcon!
SVN:trunk[2968]
2013-10-29 15:55:00 +00:00
Romain Quetiez
bc14ad9e80 Datamodel version is 2.0.2 !
SVN:trunk[2967]
2013-10-29 14:44:27 +00:00
Romain Quetiez
e81d872306 Added missing cosmetic bitmap for the portal
SVN:trunk[2966]
2013-10-29 13:54:34 +00:00
Romain Quetiez
f83bb7fa90 Fixed regression introduced with "forgot password": button to reset the user password labelled as "Send now!"
SVN:trunk[2965]
2013-10-29 13:13:58 +00:00
Romain Quetiez
032947ff03 Portal with Incident tickets: added rights on Incident for Portal users
SVN:trunk[2964]
2013-10-29 11:37:04 +00:00
Romain Quetiez
9e39013d4c Portal: service type mapping can be an empty string
SVN:trunk[2963]
2013-10-29 11:00:31 +00:00
Romain Quetiez
5d02db5440 Show the new setting portal_tickets in the default config file
SVN:trunk[2962]
2013-10-29 09:36:23 +00:00
Romain Quetiez
7300698240 Update of the readme with the latest changes
SVN:trunk[2961]
2013-10-29 09:16:29 +00:00
Erwan Taloc
a47bbb3a9a Add a version for Documents
SVN:trunk[2960]
2013-10-28 18:51:39 +00:00
Romain Quetiez
a333bcb084 User portal: enable the creation of Incident tickets (ITIL + requires a change in the configuration file -see the readme file)
SVN:trunk[2959]
2013-10-28 16:50:13 +00:00
Romain Quetiez
e92d193347 Draft of the readme for 2.0.2 beta
SVN:trunk[2958]
2013-10-25 12:06:04 +00:00
Denis Flaven
d5a0808118 Cosmetic style adjustment for the portal...
SVN:trunk[2957]
2013-10-25 09:38:13 +00:00
Denis Flaven
864ce74cbc #805 The label of the ticket must be computed at the last minute, just before insertion, for the Mutex to be effective
SVN:trunk[2956]
2013-10-24 12:05:16 +00:00
Romain Quetiez
f684cb1745 Compiler: added "constants"
SVN:trunk[2955]
2013-10-24 09:49:56 +00:00
Denis Flaven
54769aa2d1 Oops, rollback of the previous (unexpected) commit...
SVN:trunk[2954]
2013-10-24 09:28:25 +00:00
Denis Flaven
272a249d14 #805 Use a mutex to turn the insertion of a new ticket into an atomic operation
SVN:trunk[2953]
2013-10-24 09:15:41 +00:00
Denis Flaven
02e6658439 #804 tickets' highlighting is now based on the computation performed by the stopwatch, in order to support non 24x7 working hours
SVN:trunk[2952]
2013-10-24 08:11:40 +00:00
Romain Quetiez
0c327f2c36 Added a demo mode (config: demo_mode = true). In that mode, logins get read-only (even for admins)
SVN:trunk[2951]
2013-10-23 13:36:44 +00:00
Romain Quetiez
dcb5a7208a #800 No need to track that last update has changed each time the ticket gets updated (common to all types of tickets)
SVN:trunk[2950]
2013-10-22 14:05:39 +00:00
Denis Flaven
94de069963 Fixed a regression in the computation of default choices.
SVN:trunk[2949]
2013-10-22 12:52:45 +00:00
Romain Quetiez
f0c66be7cd #783, #233 and #466 The recent revision (2921 for ticket #783) introduced a significant slow down when performing CSV import (but not only). This new revision does suppress the regression, and even speeds up bulk updates in general. This revision is candidate for retrofit into branch 2.0.1 (along with 2921)
SVN:trunk[2948]
2013-10-22 12:46:09 +00:00
Denis Flaven
0b7ed90e18 Fixed a regression introduced by revision [2856]: Avoid breaking pages with tabs when there is no BASE tag at all...
SVN:trunk[2947]
2013-10-22 11:49:38 +00:00
Romain Quetiez
20ba6242e7 No time limit for long operations like: Bulk delete, CSV import (interactive) and Bulk modify
SVN:trunk[2946]
2013-10-22 08:09:34 +00:00
Erwan Taloc
015919702b Change translation of incident menu in French dictionary
SVN:trunk[2945]
2013-10-21 13:15:27 +00:00
Erwan Taloc
ae8ff6b675 Fix Trac #796
Prevent Support agent to create ticket for obsolete Services and Service sub categories

SVN:trunk[2944]
2013-10-21 13:06:10 +00:00
Erwan Taloc
0ea6657610 Add VLANs on Subnet and Physical Interfaces
SVN:trunk[2943]
2013-10-21 12:35:57 +00:00
Denis Flaven
9d6d93d42f #757 Better UI to manage direct linksets: added the ability to provide the "reverse query" by specifying a '<filter>' tag on AttributeLinkedSet.
SVN:trunk[2942]
2013-10-21 12:25:07 +00:00
Denis Flaven
4f845ec98d #780 Dictionary entries for "Check All" and "Uncheck All"
SVN:trunk[2941]
2013-10-21 09:08:32 +00:00
Erwan Taloc
f65c84300f Change translation in French dictionary
SVN:trunk[2940]
2013-10-21 07:07:19 +00:00
Erwan Taloc
d8b9679346 Change translation in French dictionary
SVN:trunk[2939]
2013-10-21 06:59:56 +00:00
Erwan Taloc
e090b866e1 Change translation in Franch dictionnary
SVN:trunk[2938]
2013-10-21 06:59:20 +00:00
Erwan Taloc
d30f34afc1 Translate menu entry in French
SVN:trunk[2937]
2013-10-21 06:57:51 +00:00
Erwan Taloc
3b7aa49ca3 Translate menu entry in French
SVN:trunk[2936]
2013-10-21 06:57:26 +00:00
Erwan Taloc
441bd44f97 Hidde unused attribute end end in the search form
SVN:trunk[2935]
2013-10-21 06:46:49 +00:00
Erwan Taloc
7a18730949 Hidde unused attribute end end in the search form
SVN:trunk[2934]
2013-10-21 06:46:17 +00:00
Erwan Taloc
0e27be0aca Hidde unused attribute end end in the search form
SVN:trunk[2933]
2013-10-21 06:45:37 +00:00
Erwan Taloc
1c16365881 Remove duplicate display of attribute service provider
SVN:trunk[2932]
2013-10-21 06:44:53 +00:00
Erwan Taloc
585e06f096 Remove duplicate display of attribute service provider
SVN:trunk[2931]
2013-10-21 06:44:24 +00:00
Romain Quetiez
edce93282b #792 Duplicate entries in the parent/child tickets when updating the case log and applying a stimulus (e.g. Close the WO) at the same time.
SVN:trunk[2930]
2013-10-18 15:54:54 +00:00
Romain Quetiez
26dca89b19 #780 Auto refresh for the dashboards (+ cosmetics changes for the shortcuts)
SVN:trunk[2929]
2013-10-18 14:14:48 +00:00
Denis Flaven
9b58e736ff #787 Added buttons to check/uncheck all options at once in multi selects inside search forms.
SVN:trunk[2928]
2013-10-18 09:06:25 +00:00
Denis Flaven
36e6a6106b #757 Better UI to manage direct linksets... on going...
SVN:trunk[2927]
2013-10-18 08:26:31 +00:00
Romain Quetiez
bbb31e2b7f #780 Auto refresh for the shortcuts
SVN:trunk[2926]
2013-10-17 14:47:05 +00:00
Romain Quetiez
afa3c40c3e Improved the column load optimization which was causing object reloads in various circumstances (impact can be important when loading lists with many lines):
1) When changing column settings (menu "Configure this list")
2) When a plugin was systematically reading some data (e.g. Highlighting late tickets requires to read tto/trr/status/start_date!)

SVN:trunk[2925]
2013-10-17 09:46:10 +00:00
Romain Quetiez
401d61aa76 Creation of indexes on several columns (exploited for a few classes when it was obvious)
SVN:trunk[2924]
2013-10-16 15:21:20 +00:00
Romain Quetiez
eda203af26 #785 Share the results of a query phrase (preview of the results in the query details page -iif it has NO parameter)
SVN:trunk[2923]
2013-10-16 11:45:17 +00:00
Romain Quetiez
3022ba9b1a Fixed regression introduced a few months ago when reworking the dashboard edition (after the release of 2.0.1)
SVN:trunk[2922]
2013-10-16 11:43:21 +00:00
Romain Quetiez
440f50259b #783 Added the placeholder $this->xxx_list$ for emailing (names separated by a new line, truncated to 100 items)
SVN:trunk[2921]
2013-10-16 10:36:15 +00:00
Romain Quetiez
5d402a5f9d Reviewed the instrumentation to help in tuning the performance (added a message in the admin banner when logging is active)
SVN:trunk[2920]
2013-10-16 10:33:30 +00:00
Denis Flaven
2d83f331e2 #771: better display for "edit in place".
SVN:trunk[2919]
2013-10-16 09:34:48 +00:00
Denis Flaven
677cc2b19e #795 Issue when using the actual (id) value of an external key as a reconciliation field
SVN:trunk[2918]
2013-10-16 08:54:22 +00:00
Romain Quetiez
caa621eb04 Reviewed the instrumentation to help in tuning the performance
SVN:trunk[2916]
2013-10-15 16:08:43 +00:00
Denis Flaven
5ea2ac3fef #741 Complete localization of the CSV import confirmation dialog
SVN:trunk[2915]
2013-10-15 16:00:51 +00:00
Denis Flaven
09318b81c0 #790 Only report as installed the modules from the previous installation, not all previous installations.
SVN:trunk[2914]
2013-10-15 15:17:50 +00:00
Denis Flaven
d5be250640 #754 Prevent UserRequests to have themselves set as Parent Request (and same for Incidents)
SVN:trunk[2913]
2013-10-15 13:41:15 +00:00
Denis Flaven
fca3bb2a73 #738 Adding a space at the end of the mailto: URL to make it better recognized by mail clients (namely Outlook)
SVN:trunk[2911]
2013-10-15 08:09:15 +00:00
Denis Flaven
bf9cb67226 #791 Protect against single quotes in localized strings...
SVN:trunk[2909]
2013-10-14 16:19:03 +00:00
Denis Flaven
e54d6ecc12 #777 mandatory fields that are external keys are now displayed with a star before the arrow: ExtkeyName*->ReconciliationField. In import the old syntax is supported as well.
SVN:trunk[2908]
2013-10-14 15:36:49 +00:00
Romain Quetiez
30de6a1e39 Instrumented the code to measure the impact of object reloads
SVN:trunk[2907]
2013-10-14 15:19:26 +00:00
Romain Quetiez
6de4d93ef2 #562 and #760 Refresh of the german translation
SVN:trunk[2906]
2013-10-14 15:10:29 +00:00
Romain Quetiez
3c3d4a073d #769 Title of pies and charts are not consistent with the title of other dashlets
SVN:trunk[2905]
2013-10-14 14:48:09 +00:00
Romain Quetiez
c2efdfa0bb #794 Could not export the field friendlyname in format 'spreadsheet'
SVN:trunk[2903]
2013-10-14 14:17:07 +00:00
Romain Quetiez
2218003bec #758 REST service: key given in clear in the returned objects (incremented the API verion to 1.1)
SVN:trunk[2902]
2013-10-14 13:52:11 +00:00
Denis Flaven
3ffd289a5e #793 provide the default '=' and '!=' operators for all types of Computed Fields.
SVN:trunk[2901]
2013-10-14 13:49:21 +00:00
Romain Quetiez
fe4d55fbf6 Missing french translation
SVN:trunk[2900]
2013-10-14 13:44:50 +00:00
Romain Quetiez
b5d9e5a8b6 #773 Display boolean values from the stop watches as yes/no (localized, like enums) + took the opportunity to enable the export in spreadsheet format
SVN:trunk[2899]
2013-10-14 13:22:52 +00:00
Romain Quetiez
046a7b0e2d Fixed a regression due to the change [2877] Tooltip to preview attachments
SVN:trunk[2898]
2013-10-14 12:15:07 +00:00
Erwan Taloc
32ca9727f7 Fix Bug #762 : Remove wrong fields approval_date approval_comments for a Routine change
SVN:trunk[2896]
2013-10-11 11:42:22 +00:00
Erwan Taloc
08fc696f94 Modify Sample data for Service categories to set them to status "production" by default
SVN:trunk[2895]
2013-10-11 11:13:13 +00:00
Erwan Taloc
39ef3d13e6 Fix bug #768 to avoid to select obsolete service and service sub categories in the portal
SVN:trunk[2894]
2013-10-11 11:05:45 +00:00
Erwan Taloc
151b300856 Fix bug #789 to add up to 12 Digit for a IPInterface
SVN:trunk[2893]
2013-10-11 10:51:51 +00:00
Erwan Taloc
c5bf962095 Fix bug #755 to prevent modification of CIs and Contacts list for UserRequest and Incidents
SVN:trunk[2892]
2013-10-11 10:47:14 +00:00
Erwan Taloc
a6a4cf5d00 Bug #742 fixed. Allow portal user to modify a ticket when it is pending
SVN:trunk[2891]
2013-10-11 10:34:47 +00:00
Erwan Taloc
90f7aa04bb Fib bug #739 preventing a Support Agent to set a UserRequest to status "Pending"
SVN:trunk[2890]
2013-10-11 10:33:32 +00:00
Romain Quetiez
bb9f074670 Show all types of Actions from the "Notifications/Actions" tab.
SVN:trunk[2889]
2013-10-11 10:30:29 +00:00
Erwan Taloc
ef26f395bd Fix bug #751. Check that class Logical Volume exists when checking dependencies of a Server
Add attribute Subnet name on Subnet element

SVN:trunk[2888]
2013-10-11 10:24:32 +00:00
Denis Flaven
e34516745c Retrofit the useful DoPostRequest function which was used (and defined) in several extensions.
SVN:trunk[2886]
2013-10-11 08:38:38 +00:00
Erwan Taloc
8474b423fe Move definition of the delivery model of an organization from itop-config-mgmt to itop-service-mgmt module.
SVN:trunk[2885]
2013-10-11 08:36:26 +00:00
Denis Flaven
30b2d93bdf Added support of different (sub)classes of notifications in the "Notifications" tab on an object.
SVN:trunk[2884]
2013-10-10 16:01:44 +00:00
Denis Flaven
7162db0487 Fix for properly computing the default choices in case of upgrade...
SVN:trunk[2883]
2013-10-09 10:23:40 +00:00
Romain Quetiez
e08fa6b43b #745 Default menu is not computed correctly (depends on the customizations made to the menu -> order of declaration)
SVN:trunk[2882]
2013-10-08 14:27:27 +00:00
Denis Flaven
4b9e6edab8 File identifiers are no longer numeric only !
SVN:trunk[2881]
2013-10-08 12:46:37 +00:00
Romain Quetiez
7017bbf88b The login web page must NOT be cached by the web browsers
SVN:trunk[2880]
2013-10-08 08:28:25 +00:00
Romain Quetiez
4f4ceeadc6 Removed calls to console.log
SVN:trunk[2879]
2013-10-08 08:23:38 +00:00
Romain Quetiez
b0ecb2f6c6 #774 Sort the enums in the selection drop-down box (search forms) -initially based on the declaration order
SVN:trunk[2878]
2013-10-08 07:34:59 +00:00
Denis Flaven
4be0837ead #782: preview (as a tooltip) for image attachments.
SVN:trunk[2877]
2013-10-03 16:53:25 +00:00
Romain Quetiez
e3832a13a6 #784 Data sync: display the attribute code (as well as its label in the user language)
SVN:trunk[2876]
2013-10-03 15:50:56 +00:00
Romain Quetiez
169f576ccf #781 Plain text dashlet shown on one single line
SVN:trunk[2875]
2013-10-03 15:40:45 +00:00
Romain Quetiez
894b59eee1 #779 It is possible to record a wrong OQL in the phrase book, but then it cannot be edited anymore!
SVN:trunk[2874]
2013-10-03 15:30:29 +00:00
Denis Flaven
fe41d09acb Support for input values with no icon.
SVN:trunk[2873]
2013-10-02 14:34:45 +00:00
Denis Flaven
387e4c6f0b Upload image button should not submit the parent form !!!
SVN:trunk[2872]
2013-10-02 12:48:22 +00:00
Romain Quetiez
6f8be14711 Internal: failed authentication to return error 401 instead of prompting the end-user (to be exploited by the ajax calls)
SVN:trunk[2871]
2013-10-02 09:30:14 +00:00
Denis Flaven
7d824dd03c Removed an obsolete comment...
SVN:trunk[2869]
2013-09-30 14:41:08 +00:00
Denis Flaven
899a7c1ba0 New pattern accepting the new global Top Level Domains (gTLD)
SVN:trunk[2867]
2013-09-27 07:29:15 +00:00
Romain Quetiez
a84eff5c3b Fixed regression introduced on fix [2829]
SVN:trunk[2866]
2013-09-26 15:32:16 +00:00
Romain Quetiez
e0ae6484d3 Fixed regression on the change on change tracking (sic!)
SVN:trunk[2865]
2013-09-26 15:22:23 +00:00
Romain Quetiez
552e90f674 Logoff: display the message in the user language (used to be 100% english)
SVN:trunk[2864]
2013-09-25 10:20:33 +00:00
Romain Quetiez
898ee016c9 Generalized the option tracking_level to any kind of attributes. Defaults to 'all', can be set to 'none' to disable the change tracking on a single attribute (LinkSets still have the same allowed values: none, list, details and all).
SVN:trunk[2863]
2013-09-25 09:47:50 +00:00
Denis Flaven
7d87aad0bb Protect the deletion of objects with very long friendly names
SVN:trunk[2861]
2013-09-24 16:19:22 +00:00
Romain Quetiez
8d068b6a93 #767 JSON/REST Reconciliations made on loose criteria (forced to strict equality, no way to specify a loose criteria)
SVN:trunk[2860]
2013-09-24 13:38:49 +00:00
Romain Quetiez
ea36d6b147 New feature: Forgot password (prerequisite in the very standard authent local module)
SVN:trunk[2859]
2013-09-24 12:48:09 +00:00
Romain Quetiez
90e024b2bb Cosmetics on the login web page (2 of 2!)
SVN:trunk[2858]
2013-09-24 12:44:31 +00:00
Romain Quetiez
955beb70e4 Cosmetics on the login web page
SVN:trunk[2857]
2013-09-24 12:43:44 +00:00
Denis Flaven
1a60b7005b Avoid breaking pages with tabs when there is no BASE tag at all...
SVN:trunk[2856]
2013-09-24 10:05:33 +00:00
Romain Quetiez
fde3808cdf New feature: Forgot password -> email to reset (possibly disabled in the config file)
SVN:trunk[2855]
2013-09-24 09:15:52 +00:00
Denis Flaven
76e0ee66ae Allow for comparisons of the module's versions in the expression of dependencies. For example one can now say "itop-config-mgmt/>=2.0.2" for a dependency.
SVN:trunk[2853]
2013-09-23 12:48:55 +00:00
Romain Quetiez
a2a0ee5194 Fixed bug in the JSON REST API: core/create and core/update, could not reset an external key (0)
SVN:trunk[2852]
2013-09-19 11:42:06 +00:00
Romain Quetiez
0bced2f9ae ModelFactory: needed / define_if_not_exists were not equivalent
SVN:trunk[2850]
2013-09-12 08:09:15 +00:00
Romain Quetiez
ccc9729cc5 #763 Could not use "configure this list" once a stop watch has been added to the list, which is a pitty because such attributes are not aimed at being displayed in lists!
SVN:trunk[2848]
2013-09-11 09:53:31 +00:00
Romain Quetiez
cf383bcf6b Fixed bug (wrong DB charset after invoking AnalyzeInstallation!)
SVN:trunk[2847]
2013-09-06 15:18:41 +00:00
Romain Quetiez
9292d5fa33 Icon select: load images when the control becomes visible
SVN:trunk[2846]
2013-09-05 16:01:07 +00:00
Denis Flaven
3b6646f1b9 Load structural data for all selected modules indepentyl of:
- the load of sample data
- first install or upgrade

SVN:trunk[2845]
2013-09-03 15:37:10 +00:00
Romain Quetiez
ca1d4d8936 Management of environments: the banner must be injected by the mean of iPageUIExtension
SVN:trunk[2844]
2013-09-02 12:54:00 +00:00
Romain Quetiez
afa6399dce Regression due to the safe compile: fixed an issue depending on the OS: could not compile (bug with PHP rename)
SVN:trunk[2843]
2013-09-02 08:52:14 +00:00
Romain Quetiez
f93b1e1c1c Module installation information always loaded within the meta model
SVN:trunk[2842]
2013-08-30 14:20:20 +00:00
Denis Flaven
fd7adb2202 Make the logo transparent (background removal)
SVN:trunk[2841]
2013-08-30 08:30:28 +00:00
Denis Flaven
05f50c285c Fixed an "Undefined variable" error
SVN:trunk[2840]
2013-08-30 08:17:22 +00:00
Romain Quetiez
0aa2dc9ce3 Portal: cosmetics (same logo as the application logo)
SVN:trunk[2839]
2013-08-30 07:38:07 +00:00
Romain Quetiez
607236a7cb Compiler: added brand management
SVN:trunk[2838]
2013-08-29 08:35:44 +00:00
Romain Quetiez
564ba105eb CRON: report that CRON is already running BEFORE saying that the DB is read-only (re-entrance during an operation done in the background)
SVN:trunk[2837]
2013-08-27 14:20:12 +00:00
Romain Quetiez
73b492e892 New mechanism: a module page can be accessed by the mean of a canonical URL (utils::GetAbsoluteUrlModulePage to build the proper URL)
SVN:trunk[2836]
2013-08-27 14:04:59 +00:00
Romain Quetiez
e99d96e081 Safe compilation (works in a temporary directory, on success then move it into env-production)
SVN:trunk[2835]
2013-08-27 12:19:55 +00:00
Romain Quetiez
abae2129ad CRON: protection against re-entrance now relies on a bullet-proof mutex. Also added the option 'debug=1' to output the call stack in case an exception occurs (not always because of passwords being shown in the call stack)
SVN:trunk[2834]
2013-08-26 15:29:32 +00:00
Romain Quetiez
f8c3e0ddea Model factory: fixed two bugs
SVN:trunk[2833]
2013-08-23 14:37:43 +00:00
Romain Quetiez
a28a0aba7d Compiler: when creating a test environment, take the relevant delta file (source env != dest env)
SVN:trunk[2832]
2013-08-23 14:36:33 +00:00
Romain Quetiez
358911604b #752 Notifications sent several times (or too late) when MySQL is hosted on another server
SVN:trunk[2831]
2013-08-23 07:43:10 +00:00
Romain Quetiez
75eb44912f Setup: Source dir recorded with a trailing backslash under windows
SVN:trunk[2829]
2013-08-22 11:57:31 +00:00
Romain Quetiez
2893d16d58 Restored the behavior of itop-sla-computation (if present, then it becomes the default working hour computer)
SVN:trunk[2828]
2013-08-21 08:07:31 +00:00
Romain Quetiez
2dbcb6d416 Restored the logo on the portal (picture with transparent background)
SVN:trunk[2827]
2013-08-20 12:11:53 +00:00
Romain Quetiez
2b4ad2c50b Reviewed the portal (look and feel slightly improved)
SVN:trunk[2826]
2013-08-20 11:42:46 +00:00
Romain Quetiez
7e4b69d272 Improved the error reporting for the backup (in case mysqldump fails with a single error, then the error is displayed directly)
SVN:trunk[2825]
2013-08-19 15:16:32 +00:00
Romain Quetiez
d8c9044e15 Improved the error reporting for the backup (in case mysqldump fails with a single error, then the error is displayed directly)
SVN:trunk[2824]
2013-08-19 15:15:53 +00:00
Denis Flaven
b2e4cf2c09 Add a carriage return to the error message output when iTop is NOT yet installed.
SVN:trunk[2823]
2013-08-19 14:59:41 +00:00
Romain Quetiez
08fa8362e3 CRON: reschedule at startup IIF the task is inactive or it is planned in the future
SVN:trunk[2822]
2013-08-14 15:01:53 +00:00
Romain Quetiez
447736f585 CRON to exit gracefully if iTop not yet installed
SVN:trunk[2821]
2013-08-14 13:21:41 +00:00
Denis Flaven
5ed91c2223 New verb "AfterDatabaseSetup" for performing installation tasks after the completion of the DB creation (+predefined objects & admin account)
SVN:trunk[2820]
2013-08-14 07:34:07 +00:00
Romain Quetiez
4fa07536d5 Added datamodel delta (if any) to the backup file
SVN:trunk[2819]
2013-08-13 09:01:04 +00:00
Romain Quetiez
8881450d59 Delta revision id can be stored into the XML delta
SVN:trunk[2818]
2013-08-09 15:53:42 +00:00
Romain Quetiez
58af5528be Possibility to introduce a delta (not in a module) at compile time
SVN:trunk[2817]
2013-08-09 15:47:05 +00:00
Romain Quetiez
98a1242050 New capability for CRON: handle tasks scheduled at given date/time (as opposed to a task being executed more or less continuously).
SVN:trunk[2816]
2013-08-08 15:23:05 +00:00
Denis Flaven
9536c99422 Allow "Support Agents" to put an Incident in "Pending" state.
SVN:trunk[2814]
2013-08-01 08:24:19 +00:00
Denis Flaven
7cfd5ad2a3 Ugly fix for a nasty change in jQuery UI behavior: UI tabs were considered as "Ajax" tabs when the page has a "base" tag, which was not the case in previous versions. Cf http://bugs.jqueryui.com/ticket/8637
SVN:trunk[2813]
2013-07-31 16:45:33 +00:00
Denis Flaven
86ba340204 #747: protects against the non-existence of the UserRequest class (which is not always installed).
SVN:trunk[2811]
2013-07-30 16:24:52 +00:00
Denis Flaven
b32a142e14 Use the minified version of jquery-migrate, since the non-minified version (which produces debug traces) is excluded from the build.
SVN:trunk[2810]
2013-07-25 09:48:58 +00:00
Denis Flaven
7e45f34a86 #746 allow adding an AttributeBlob with is_null_allowed = true to an existing Data Model. (same issue fixed also for AttributeOneWayPassword).
SVN:trunk[2808]
2013-07-25 09:17:16 +00:00
Denis Flaven
1064feaa8e Properly handle nested forms in "PropertySheet" and "read-only" mode
SVN:trunk[2807]
2013-07-24 17:01:25 +00:00
Denis Flaven
17658d1b6a Bug fix: validation was broken when the first fields were not Ok.
SVN:trunk[2806]
2013-07-24 17:00:30 +00:00
Denis Flaven
ce643d9086 Export the content of the CaseLogs in "spreadsheet" format, with some tricks to preserve the formatting in Excel.
SVN:trunk[2804]
2013-07-17 16:54:27 +00:00
Denis Flaven
481515b419 IsInDefinition is needed => made it public
SVN:trunk[2803]
2013-07-17 14:48:10 +00:00
Denis Flaven
6d60d92b03 Fix for a non localized message.
SVN:trunk[2802]
2013-07-17 08:46:50 +00:00
Denis Flaven
3edbdf76f3 Fix for a non localized message.
SVN:trunk[2801]
2013-07-17 08:46:04 +00:00
Denis Flaven
80bac5275c Forms enhancements:
- The current value of a field is automatically excluded from the forbidden values


SVN:trunk[2800]
2013-07-16 09:16:54 +00:00
Denis Flaven
59fc9e24d9 Forms enhancements:
- The current value of a field is automatically excluded from the forbidden values
- Several levels of subforms can be nested, even when displaying as a property sheet
- Sortables fields re-implemented based on a widget.

SVN:trunk[2799]
2013-07-16 09:16:12 +00:00
Denis Flaven
7db7c0781f Make the portal (slightly) more configurable...
SVN:trunk[2797]
2013-07-12 13:55:28 +00:00
Romain Quetiez
358ddf6019 Fixed issue for the toolkit
SVN:trunk[2796]
2013-07-10 08:48:08 +00:00
Romain Quetiez
ebf08345af Forms: added the possibility to specify forbidden values + message to explain the issue(toolip) (fiwed a bug on the previous implementation, causing a javascript error, hence a stopper regression due to missing event binds)
SVN:trunk[2795]
2013-07-09 09:09:49 +00:00
Denis Flaven
a9ad236439 Allow filtering the Delta output...
SVN:trunk[2794]
2013-07-08 08:48:03 +00:00
Romain Quetiez
3066240ca0 Forms: added the possibility to specify forbidden values + message to explain the issue(toolip)
SVN:trunk[2793]
2013-07-08 08:25:13 +00:00
Romain Quetiez
d82326bfd4 Form as a dialog, possibility to specify an introduction message (cosmetics)
SVN:trunk[2792]
2013-07-05 13:57:07 +00:00
Romain Quetiez
f99ecb40d0 Form as a dialog, possibility to specify an introduction message
SVN:trunk[2791]
2013-07-05 13:54:16 +00:00
Romain Quetiez
d7124123e9 Compiler: allow to set the flags enable_class/enable_action etc. for a TemplateMenuNode (already taken into account at runtime)
SVN:trunk[2790]
2013-07-04 14:17:23 +00:00
Romain Quetiez
e517f2b6f5 Cosmetics on the dashboards
SVN:trunk[2789]
2013-07-03 15:52:31 +00:00
Denis Flaven
ea686059b6 Protect against non existing reconciliation keys...
SVN:trunk[2788]
2013-07-03 09:47:44 +00:00
Denis Flaven
3898371d44 Added support of CSS classes for styling the form
SVN:trunk[2787]
2013-07-03 09:46:16 +00:00
Romain Quetiez
f0a5a0a948 Completed the move of dashboards from separate definition files (e.g. overview.xml) into data model files (8 dashboards were concerned on the model 2.x, 6 for the model 1.x)
SVN:trunk[2786]
2013-07-02 09:18:40 +00:00
Denis Flaven
24ab96769a Re-position the popup menu each time the button is clicked, in case the button was moved...
SVN:trunk[2785]
2013-06-28 09:39:57 +00:00
Romain Quetiez
5e3a34d425 Updated the licences: 2012 -> 2013
SVN:trunk[2784]
2013-06-28 07:19:55 +00:00
Romain Quetiez
76724225e0 XML format: missing/wrong id on dashboards cells
SVN:trunk[2783]
2013-06-28 07:11:24 +00:00
Romain Quetiez
3ab539e2ba Dashboard re-engineering
SVN:trunk[2782]
2013-06-27 15:21:35 +00:00
Romain Quetiez
721a654152 Fixed regression due to the update of JQuery UI
SVN:trunk[2781]
2013-06-26 16:25:53 +00:00
Denis Flaven
57e51e44f1 Make sure that tabs (and tab panels) are properly identified
SVN:trunk[2780]
2013-06-26 13:56:24 +00:00
Denis Flaven
f7642283f3 Forget about IE 8 support ???
SVN:trunk[2779]
2013-06-26 13:55:27 +00:00
Denis Flaven
e7897b9139 Update for jQuery UI 1.10
SVN:trunk[2778]
2013-06-26 13:54:52 +00:00
Romain Quetiez
59ce84f7cb Cosmetics on the portal
SVN:trunk[2776]
2013-06-14 15:37:47 +00:00
Romain Quetiez
bb6d87e8ed Cosmetics on the portal
SVN:trunk[2775]
2013-06-14 15:15:13 +00:00
Romain Quetiez
46dae2f06f Cosmetics: User portal - added a title (usually, this would appear on the tab of your browser)
SVN:trunk[2774]
2013-06-14 15:05:03 +00:00
Romain Quetiez
0c1a366c07 OQL normalization and dashlets have been made independent from the class MetaModel (adjusted the API)
SVN:trunk[2773]
2013-06-13 14:11:13 +00:00
Romain Quetiez
71cc6f7e6b OQL normalization and dashlets have been made independent from the class MetaModel (reviewed the API)
SVN:trunk[2772]
2013-06-12 07:21:11 +00:00
Romain Quetiez
ba9a50b6fb #736 Could not delete objects unless you are authorized to bulk delete
SVN:trunk[2769]
2013-06-07 07:28:31 +00:00
Romain Quetiez
9ef41a37b8 OQL normalization and dashlets have been made independent from the class MetaModel (2 of 2!)
SVN:trunk[2768]
2013-06-03 13:49:51 +00:00
Romain Quetiez
26db86beb2 OQL normalization and dashlets have been made independent from the class MetaModel
Added OQL normalization unit tests (to be run on a standard installation)

SVN:trunk[2767]
2013-06-03 13:26:14 +00:00
Denis Flaven
69c37b07de Upgrade to jQuery 1.10 and jQuery UI 1.10
SVN:trunk[2766]
2013-05-30 09:48:59 +00:00
Denis Flaven
7844db0719 Upgrade to jQuery 1.10 and jQuery UI 1.10
SVN:trunk[2765]
2013-05-30 09:13:43 +00:00
Denis Flaven
6edb1e3482 Removed the use of the obsolete $.browser property, since we don't care about IE 7 anyway.
SVN:trunk[2764]
2013-05-30 08:11:12 +00:00
Denis Flaven
7b887f3ea5 #734 Fixed a regression on reconciliation keys during CSV import.
SVN:trunk[2761]
2013-05-29 08:53:07 +00:00
Denis Flaven
2fe407967b Preparing for the 2.0.1 release... update of the readme file.
SVN:trunk[2758]
2013-05-22 09:55:29 +00:00
Denis Flaven
703be73c95 #689: Logoff / Change Pwd buttons not visible in the portal if the window is too small: now allow line wrapping...
SVN:trunk[2757]
2013-05-22 09:43:26 +00:00
Denis Flaven
3060462edc #732: Change password: exit after building the page in case of wrong "old" password
SVN:trunk[2756]
2013-05-22 08:43:48 +00:00
Romain Quetiez
263acaf4e4 Missing localized label for some problem management menus, when the Known Error Management module is installed without the Problem Management module
SVN:trunk[2755]
2013-05-21 13:13:44 +00:00
Denis Flaven
db1be8f500 Add "Employee number" to the list of possible reconciliation keys for the class Person.
SVN:trunk[2754]
2013-05-21 11:04:01 +00:00
Romain Quetiez
452eca5288 The ticket class must be abstract!
SVN:trunk[2753]
2013-05-21 10:49:22 +00:00
Romain Quetiez
a728dfcf48 All the overloads of DisplayBareProperties must return an array (otherwise a warning is issued and field validation will not work as expected)
SVN:trunk[2752]
2013-05-21 10:44:13 +00:00
Denis Flaven
2027dc4a3d Make icons transparent...
SVN:trunk[2751]
2013-05-17 12:12:19 +00:00
Denis Flaven
1bc4e1431c Preparing for the 2.0.1 release... update of the readme file.
SVN:trunk[2750]
2013-05-17 08:34:42 +00:00
Denis Flaven
9afe28be20 Support non scalar posted parameters...
SVN:trunk[2749]
2013-05-16 15:45:57 +00:00
Denis Flaven
8073120351 JS syntax cleanup...
SVN:trunk[2748]
2013-05-16 15:44:05 +00:00
Denis Flaven
86c5b3e258 Typo...
SVN:trunk[2747]
2013-05-16 15:42:34 +00:00
Denis Flaven
b971faecda Cleanup of the log output. No need for such verbosity now that we are approaching the release.
SVN:trunk[2746]
2013-05-16 15:41:52 +00:00
Denis Flaven
2708b0de0e #728: the ticket's title is now an (external) attribute of the link between a ticket and a CI.
SVN:trunk[2745]
2013-05-16 14:28:23 +00:00
Romain Quetiez
8dd9893202 Readme: Documented the known issue #730 Leaving the backup temp files on disk
SVN:trunk[2744]
2013-05-16 14:23:35 +00:00
Denis Flaven
48d740da25 #727: prevent a crash in cron.php
SVN:trunk[2743]
2013-05-16 14:05:38 +00:00
Romain Quetiez
7ba5526fda #725 Specify a port for mySQL server
#729 Backup fails on Windows IIS

SVN:trunk[2742]
2013-05-16 13:57:52 +00:00
Denis Flaven
58dfa3335a Spanish localization enhancements, thanks to Miguel Turrubiates
SVN:trunk[2741]
2013-05-16 12:41:10 +00:00
Denis Flaven
deec1aa2a2 Fix the auto-resize (fit) of the dialog's content.
SVN:trunk[2740]
2013-05-16 12:13:18 +00:00
Romain Quetiez
a62c1946a6 #721 Unmet dependencies not detected
SVN:trunk[2739]
2013-05-16 08:54:50 +00:00
Romain Quetiez
a194308486 #726 Missing string in german dictionary
SVN:trunk[2738]
2013-05-16 07:41:36 +00:00
Denis Flaven
2e442dbaa0 #704: preserve the content of a "linkedset" when changing the initial state of the object being created !
SVN:trunk[2737]
2013-05-15 15:56:42 +00:00
Romain Quetiez
ad9ed96960 #673 Could not create a physical interface with default value for the speed (+ definitive corruption of the DB for the device on which the interface is being created!)
SVN:trunk[2735]
2013-05-15 10:23:06 +00:00
Romain Quetiez
efc3b4df07 Silently discard unknown attributes in object templates (in case some modules have not been installed)
SVN:trunk[2733]
2013-05-15 10:18:55 +00:00
Romain Quetiez
d6da043a32 #722 Document preview disappeared since 2.0 (not in 1.x)
SVN:trunk[2732]
2013-05-14 15:43:22 +00:00
Romain Quetiez
10a7a5aa38 Fix for reconciliation by id (Advanced Mode) -reintegrated from branch 2.0
SVN:trunk[2731]
2013-05-14 15:19:12 +00:00
Romain Quetiez
5684f1e196 #713 URL format reviewed:
- more formats allowed by default
- aligned between the wiki formatting and the URL attribute
- configurable globally with 'url_validation_pattern'
- can be defined at the attribute level with tag validation_pattern


SVN:trunk[2730]
2013-05-14 14:20:00 +00:00
Romain Quetiez
2376a63d18 #711 Audit error drill-down not working when zero error (requires to bookmark the link when there are some errors, then use it later when there is no error anymore)
SVN:trunk[2729]
2013-05-14 10:41:44 +00:00
Romain Quetiez
744b821d03 #710 Query phrase book "fields list" is too small
SVN:trunk[2728]
2013-05-14 10:19:33 +00:00
Romain Quetiez
f3eb6b5cb3 #705 Typo in the english dictionary
SVN:trunk[2727]
2013-05-14 10:09:42 +00:00
Romain Quetiez
d973f64576 #708 Cosmetics for the english dictionary
SVN:trunk[2726]
2013-05-14 10:07:20 +00:00
Denis Flaven
a6c9bcf780 #717: prevent flicker of unstyled content: make the pane visible only when their content is ready... + watchdog in case of error.
SVN:trunk[2725]
2013-05-14 10:03:23 +00:00
Romain Quetiez
7cae338e6d #723 Cron locking is too restrictive
SVN:trunk[2724]
2013-05-14 09:34:08 +00:00
Denis Flaven
ed344650c5 Prevent logoff problems depending on PHP's session.cache_limiter setting...
SVN:trunk[2723]
2013-05-13 15:45:10 +00:00
Romain Quetiez
2d03e95ece Fixed typos in the sample data for the service subcategories
SVN:trunk[2722]
2013-05-13 14:50:56 +00:00
Denis Flaven
69179f5d25 #707: oops, prevent a warning when no dict entry...
SVN:trunk[2721]
2013-05-13 14:50:26 +00:00
Romain Quetiez
27cf82b270 #693 Portal user cannot choose services linked via customer contract
SVN:trunk[2720]
2013-05-13 14:46:06 +00:00
Denis Flaven
d28891eaf4 #707: Make sure that DOCTYPE is the first statement of the page
- Enhancement: support for "target" in PopupMenuItems.. which was ignored before

SVN:trunk[2719]
2013-05-13 14:43:16 +00:00
Erwan Taloc
b1c1e5f9f2 Limit rack according to location for PDU (see track #638)
SVN:trunk[2718]
2013-05-13 10:56:55 +00:00
Erwan Taloc
8a86c6a637 change path for incident icon trac #699
SVN:trunk[2717]
2013-05-13 10:50:00 +00:00
Denis Flaven
631811145f Experimental support of icon uploads...
SVN:trunk[2716]
2013-05-07 17:27:37 +00:00
Denis Flaven
78ff062787 Proper reset of APC cache upon compilation !
SVN:trunk[2715]
2013-05-07 17:25:24 +00:00
Denis Flaven
427f50b390 Allow filtering of the languages to install
SVN:trunk[2713]
2013-05-02 08:09:10 +00:00
Romain Quetiez
21f0d96146 New way to compile the dictionaries, allowing for incremental modification via XML
SVN:trunk[2712]
2013-04-30 16:14:37 +00:00
Romain Quetiez
7fcf922ee0 Updated the unit test for the OQL parser
SVN:trunk[2711]
2013-04-29 09:01:06 +00:00
Denis Flaven
22dc44c9e5 Typo
SVN:trunk[2710]
2013-04-29 08:55:22 +00:00
Denis Flaven
6feb62d728 #383: support negative numbers in OQL
Enhancements: support MySQL bitwise operators (&, |, ^, <<, >>) and hexadecimal numbers (up to 64-bit).

SVN:trunk[2709]
2013-04-29 08:51:01 +00:00
Denis Flaven
29060f7b5e Enhancement: the expression of dependencies between modules can now use a complex boolean expression with a combination of "logical or" (||) and "logical and" (&&) instead of just a module name.
SVN:trunk[2708]
2013-04-29 08:39:23 +00:00
Denis Flaven
6df6af0df0 #663: Fix for emptying a directory which contains broken symbolic links
SVN:trunk[2707]
2013-04-29 08:34:19 +00:00
Romain Quetiez
d433a45200 #692 Compensate the effect of this fix committed as 2705
SVN:trunk[2706]
2013-04-26 10:32:11 +00:00
Romain Quetiez
d5e57ba0ba #692 Allow to set a default value for AttributeDuration (was forced to 0 anytime)
SVN:trunk[2705]
2013-04-26 10:24:44 +00:00
Romain Quetiez
d69236cb25 #701 Portal customization : enabled the calendar widget for input of a date+time type of attribute (it was only available for pure DATE attributes)
SVN:trunk[2704]
2013-04-26 10:07:13 +00:00
Romain Quetiez
422aa5b407 #715 Finalized the fix (added the year for group by day, but it is not displayed)
SVN:trunk[2703]
2013-04-26 09:54:39 +00:00
Romain Quetiez
f97f51b895 #715 Group by day -> month+day, group by month -> year+month, months are shown as a localized label
SVN:trunk[2702]
2013-04-26 09:46:31 +00:00
Denis Flaven
4ea0093f12 Enhancements to the Japanese localization, thanks to Shoji Seki
SVN:trunk[2701]
2013-04-25 09:24:35 +00:00
Romain Quetiez
2e35bb97d0 Compiler: added the capability to redefine icons in xml
SVN:trunk[2700]
2013-04-24 16:02:24 +00:00
Romain Quetiez
519b9f1a73 Needed => define if not exists
SVN:trunk[2699]
2013-04-24 15:50:44 +00:00
Denis Flaven
85c1f0d1aa New internal flag for generating the delta.
SVN:trunk[2698]
2013-04-23 15:33:40 +00:00
Romain Quetiez
f2738a79a0 User Portal:
- Portal power users can see ALL the tickets of the current customer
- Reopen and Close are done in a separate dialog box where the user will input requested data
- On closed tickets, the user satisfaction and comments are displayed

SVN:trunk[2697]
2013-04-18 15:23:43 +00:00
Romain Quetiez
4ae69372d4 User rights:
- for link classes, if no grant has been given explicitely, then check if one of the remote class has a grant (mix of compile-time and run-time changes)
- fixed an issue: when looking for a grant into the hierarchy, the most explicit declaration (aka the deepest class) must be found first -> reorder the parent classes (new flag on MetaModel::EnumParentClasses

SVN:trunk[2696]
2013-04-18 09:03:08 +00:00
Romain Quetiez
cce2509b2e #702 The second implementation of this fix was still incomplete (not compatible with some constraint queries)
SVN:trunk[2694]
2013-04-17 16:11:45 +00:00
Romain Quetiez
aa4d396960 #702 The first implementation of this fiw was not incomplete (not compatible with some constraint queries)
SVN:trunk[2692]
2013-04-17 15:53:34 +00:00
Romain Quetiez
5f11cc4cf8 #703 HTML entities not escaped in history tab
SVN:trunk[2690]
2013-04-16 13:23:10 +00:00
Denis Flaven
956ecda77e Fix for the "Notifications" tab: use the polymorphism to let each trigger determine which object is "In Scope" and thus can potentially have notifications related to it
SVN:trunk[2688]
2013-04-16 12:52:43 +00:00
Erwan Taloc
bb554b1271 define correct file name for en , es_cr, fr dictionnaries
Remove obsolete dictionaries

SVN:trunk[2685]
2013-04-16 11:44:51 +00:00
Erwan Taloc
15000ff48a remove unused dictionaries
SVN:trunk[2684]
2013-04-16 11:38:52 +00:00
Erwan Taloc
14d0ebdd71 define correct file name for en , es_cr, fr dictionnaries
SVN:trunk[2683]
2013-04-16 11:36:52 +00:00
Romain Quetiez
c95b8cf939 #702 Ignoring the class when looking up for a polymorphic ext key
SVN:trunk[2681]
2013-04-16 11:24:34 +00:00
Denis Flaven
74575440d7 Bug fix: properly serialize Emails with binary Attachments. Previously asynchronous emails with binary attachements were not working (they were queued but never sent).
SVN:trunk[2679]
2013-04-16 09:05:56 +00:00
Erwan Taloc
fcb7798e9e Remove dictionaries from module definition
SVN:trunk[2677]
2013-04-11 15:16:10 +00:00
Erwan Taloc
b0a054ada7 Remove dictionaries from module definition
SVN:trunk[2676]
2013-04-11 15:15:09 +00:00
Denis Flaven
b83d42efee Bug fix: allow printing lists fully expanded without the "pager" navigation.
Also try to completely hide the main menu on the left when printing.

SVN:trunk[2675]
2013-04-08 11:57:07 +00:00
Romain Quetiez
2e18c96328 Reviewed the readme file
SVN:trunk[2673]
2013-04-02 14:23:42 +00:00
Denis Flaven
2daccbe29d One more bug fix listed.
SVN:trunk[2672]
2013-04-02 14:13:09 +00:00
Denis Flaven
51d9c30315 #696: the message "Please fill all mandatory fields" is now localized (done in English, French and German)
SVN:trunk[2671]
2013-04-02 13:51:05 +00:00
Romain Quetiez
3d79e3fe8f #472 REST API: finalized the implementation of core/get_related + added the support for AttributeBlob (to manage documents and attachments)
SVN:trunk[2669]
2013-04-02 13:31:52 +00:00
Denis Flaven
57b34d2d91 Preparing for the 2.0.1-beta release...
SVN:trunk[2668]
2013-04-02 10:04:57 +00:00
Denis Flaven
a5a8863b52 Fixed transparent background issues...
SVN:trunk[2667]
2013-04-02 09:48:01 +00:00
Denis Flaven
d92b0aabee First (draft) version of the get_related verb for the REST API.
SVN:trunk[2666]
2013-04-02 09:42:26 +00:00
Denis Flaven
48472b6150 - Added support for creating symbolic links via the toolkit
- Added more debug info in the setup.log about the detection of the previously installed modules

SVN:trunk[2665]
2013-03-29 15:44:06 +00:00
Denis Flaven
fc29424600 #698 SeparatorPopupMenuItem was not working.
SVN:trunk[2664]
2013-03-29 13:53:51 +00:00
Denis Flaven
c4942dd747 #697: properly export NULL dates in "spreadsheet" format.
SVN:trunk[2662]
2013-03-29 13:32:47 +00:00
Denis Flaven
30b9afe165 REST API bug fix: properly handle key->finalclass
SVN:trunk[2661]
2013-03-29 10:54:23 +00:00
Denis Flaven
6d66969ff3 Better display of empty dates in the status report
SVN:trunk[2660]
2013-03-28 10:01:21 +00:00
Denis Flaven
667f258ec2 Preserve POSted parameters on the login web page (useful when the session expires)
SVN:trunk[2659]
2013-03-28 09:59:49 +00:00
Denis Flaven
badff05995 More readable edition for AttributeDuration (number are right justified)
SVN:trunk[2658]
2013-03-28 09:58:28 +00:00
Denis Flaven
052128da81 #694: make $__comp_menus__ really global !
SVN:trunk[2655]
2013-03-25 13:31:51 +00:00
Romain Quetiez
57629051bd #691 Notifications not sent if some recicipients have an empty address
SVN:trunk[2653]
2013-03-21 14:42:34 +00:00
Romain Quetiez
2cc89ad167 #690 XML export broken
SVN:trunk[2651]
2013-03-20 17:26:40 +00:00
Romain Quetiez
387ab05fd2 #674 request_type:servicerequest changed into service_request - added the DB update to allow an upgrade
SVN:trunk[2650]
2013-03-20 15:51:13 +00:00
Romain Quetiez
5de85c9d97 #688 ... regression in the search forms when the autocomplete is active
SVN:trunk[2648]
2013-03-20 15:10:18 +00:00
Romain Quetiez
d7fa2ca5b9 #688 When the autocomplete is activated, and the allowed values depend on another value, then it is possible to set a wrong value
SVN:trunk[2646]
2013-03-20 14:47:52 +00:00
Romain Quetiez
3d2866a2a0 #687 Finalized the bug fixed started in [2632], about attributes/hierarchies/labels (dictionary)
SVN:trunk[2644]
2013-03-20 10:00:19 +00:00
Denis Flaven
5d805a123e #683 : allow installation on a DB which names begins with numbers
+ directory cleanup for supporting symblinks

SVN:trunk[2639]
2013-03-19 11:38:53 +00:00
Erwan Taloc
584e1fade0 Add reconciliation keys for SLT in order to allow import for SLT having the same name
SVN:trunk[2638]
2013-03-18 17:49:27 +00:00
Erwan Taloc
98fa1fd071 Add reconciliation keys for SLT in order to allow import for SLT having the same name
SVN:trunk[2637]
2013-03-18 17:48:43 +00:00
Erwan Taloc
2b0b34a3a6 Remove wrong dependency to service_id on parent_request_id
SVN:trunk[2636]
2013-03-18 17:47:31 +00:00
Erwan Taloc
a3213bba43 Remove wrong dependency to service_id on parent_request_id
SVN:trunk[2635]
2013-03-18 17:46:56 +00:00
Erwan Taloc
1fc6b945f6 Add reconciliation key for Software (Fix Trac #666)
SVN:trunk[2634]
2013-03-18 17:46:02 +00:00
Denis Flaven
58aaed567f Bug fix: Data model alternate options were not properly checked when upgrading (especially when selecting ITIL tickets)
SVN:trunk[2633]
2013-03-18 17:39:26 +00:00
Romain Quetiez
51628604bf #687 Label for attribute Person:name always shown in english (Last Name)
SVN:trunk[2632]
2013-03-18 17:38:28 +00:00
Romain Quetiez
a35ff29363 #677 Cosmetics in the german localization (and a few other languages): first header of the config mgmt overview
SVN:trunk[2631]
2013-03-18 17:00:14 +00:00
Romain Quetiez
b52be60776 #684 CSV import / reconciliation using an enum does not take the translation into account
SVN:trunk[2630]
2013-03-18 10:26:14 +00:00
Romain Quetiez
d60d634208 #682 Order notifications (last first).
SVN:trunk[2629]
2013-03-15 16:25:02 +00:00
Romain Quetiez
b112597df8 Compiler: typo preventing from setting the property 'min_autocomplete_chars' on an external key
SVN:trunk[2628]
2013-03-15 10:07:50 +00:00
Romain Quetiez
a965bbd39f #626 Fixed missing translation in dictionaries (Tickets: "relations", and Contacts overview / count)
SVN:trunk[2627]
2013-03-14 17:37:19 +00:00
Romain Quetiez
cc70570e65 #657 JavaScript error when modifying UserLDAP object with Sync
SVN:trunk[2626]
2013-03-14 14:46:51 +00:00
Romain Quetiez
c1fae7fd24 #659 exception handling producing notices, hence causing confusion
SVN:trunk[2625]
2013-03-14 14:22:27 +00:00
Romain Quetiez
d620516055 #675 Error when drilling down on graph/pie/table with group by on a field that can be null (this case has been excluded)
SVN:trunk[2624]
2013-03-14 13:00:28 +00:00
Romain Quetiez
0918c81d58 #680 Setup failing to display the check report when DOM extension not enabled (php-xml not installed on redhat distributions)
SVN:trunk[2623]
2013-03-14 10:14:53 +00:00
Romain Quetiez
39b79e2a05 #660 Warning raised with ZendServer (with APC cache enabled) causing the setup to fail
SVN:trunk[2622]
2013-03-14 10:12:05 +00:00
Romain Quetiez
3340ca2b10 #679 Improved the reporting in case of an error while loading a module: 1) the list of already loaded modules is given, 2) the full path of the searched node is given
SVN:trunk[2621]
2013-03-14 09:07:35 +00:00
Denis Flaven
67dc148069 Better error reporting when loading a module fails.
SVN:trunk[2620]
2013-03-13 16:54:32 +00:00
Denis Flaven
c6ba656f1d Sort the modules before processing them for dependencies, in order to obtain a predictable result independent from the order of the modules in the file system... hopefully... (should fix Trac#679)
SVN:trunk[2619]
2013-03-13 16:52:10 +00:00
Romain Quetiez
acf4c7a28a #664 Could not login after upgrade of an iTop 1.x with a DB prefix
SVN:trunk[2618]
2013-03-13 14:37:46 +00:00
Romain Quetiez
b38dea4bba #634 Detection of HTTPS not working with nginx (iTop always considering the current connection as being secure)
SVN:trunk[2617]
2013-03-13 13:57:51 +00:00
Romain Quetiez
3cf398618e #472 REST Services: added core/delete (to bulk delete, full-featured), and validated the operation core/apply_stimulus
SVN:trunk[2616]
2013-03-13 11:01:16 +00:00
Romain Quetiez
462f163d8a #661 and #662 Could not create a user request (or ?) as soon as the autocomplete feature gets active
SVN:trunk[2615]
2013-03-12 16:09:03 +00:00
Erwan Taloc
96b3e9a891 Fix issue for creation of Logical Interface
SVN:trunk[2614]
2013-03-10 09:08:04 +00:00
Erwan Taloc
b56242808d Fix bug TTO / TTR computation for Service request (Trac 674)
SVN:trunk[2613]
2013-03-10 08:50:42 +00:00
Erwan Taloc
793f94d302 Correction to display IP field for Physical Device :
Network Device
Server
Storage System
NAS
Tape Library
SAN switch

SVN:trunk[2612]
2013-03-10 08:46:57 +00:00
Romain Quetiez
a23569e0c4 Fixed issue with the new CRON execution pattern
SVN:trunk[2611]
2013-03-08 14:03:55 +00:00
Romain Quetiez
80a8b63498 Modified the mechanism to display object dedicated messages (allows the plugin to add their message or replace standard ones)
Factorized the code to bulk update / bulk delete objects in an interactive way.

SVN:trunk[2610]
2013-03-08 13:36:31 +00:00
Denis Flaven
eee8d71381 Enhancement: take into account the "periodicity" of the background processes.
Bug fix: fixed a warning in CheckStopWatchThresholds

SVN:trunk[2609]
2013-03-01 17:14:54 +00:00
Denis Flaven
9013910cec Protect against unwanted output that may corrupt the JSON results
SVN:trunk[2607]
2013-02-27 16:14:16 +00:00
Denis Flaven
ddff9180ac Added 'core/apply_stimulus' as a possible operation for the REST web services.
SVN:trunk[2605]
2013-02-27 15:58:16 +00:00
Denis Flaven
4988d6eb04 Allow retrieving of the Filter used by a DisplayBlock (useful for extending search forms)
SVN:trunk[2603]
2013-02-27 14:31:27 +00:00
Denis Flaven
5ff86a40d9 Fix for making iUIPageExtension usable !
SVN:trunk[2600]
2013-02-27 13:37:12 +00:00
Denis Flaven
05133aa319 Fix for supporting the CSV export of big audit results.
SVN:trunk[2598]
2013-02-06 15:44:59 +00:00
Denis Flaven
626e2a1db1 Enable support of databases which name either is a reserved word or contains non-alphanumeric characters (i.e. itop-production).
SVN:trunk[2595]
2013-01-31 15:14:23 +00:00
Romain Quetiez
59e1a64f2d REST services: fixed issue with returned external keys
SVN:trunk[2594]
2013-01-30 14:09:32 +00:00
Denis Flaven
a9ac7d9e10 Added support for GET/JSON-P
SVN:trunk[2593]
2013-01-30 13:49:12 +00:00
Romain Quetiez
7b2789479d REST services: alpha2. It is now extensible (implement iRestServiceProvider). Still lacks two verbs: apply_stimulus and delete.
SVN:trunk[2592]
2013-01-30 09:46:55 +00:00
Denis Flaven
4e8db37060 Added credits to Stephan Rickauer about security.
SVN:trunk[2591]
2013-01-22 17:55:20 +00:00
Denis Flaven
80b0a8b942 Fix for Trac #670: XSS vulnerability issue.
SVN:trunk[2589]
2013-01-22 17:39:16 +00:00
Romain Quetiez
32924bc054 REST services: an alpha version. It is already possible to create/update/get objects. An example illustrates the possibilities.
SVN:trunk[2586]
2013-01-16 13:57:51 +00:00
Romain Quetiez
96530a5bdf Auto-documentation: first step => the extension APIs
SVN:trunk[2585]
2013-01-04 13:43:52 +00:00
Denis Flaven
18eee44ee6 Bug fix: properly record history of Hierarchical Keys
SVN:trunk[2584]
2012-12-21 15:37:45 +00:00
3115 changed files with 423721 additions and 67872 deletions

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -210,6 +210,25 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oLnk->DBDelete();
}
}
/**
* Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
* for the given attribute in the current state of the object
* @param $sAttCode string $sAttCode The code of the attribute
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
* @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used
* @return integer Flags: the binary combination of the flags applicable to this attribute
*/
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{
$iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
if (MetaModel::GetConfig()->Get('demo_mode'))
{
$aReasons[] = 'Sorry, profiles are read-only in the demonstration mode!';
$iFlags |= OPT_ATT_READONLY;
}
return $iFlags;
}
}
@@ -252,6 +271,19 @@ class URP_UserProfile extends UserRightsBaseClassGUI
{
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile'));
}
public function CheckToDelete(&$oDeletionPlan)
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults();
return false;
}
return parent::CheckToDelete($oDeletionPlan);
}
}
class URP_UserOrg extends UserRightsBaseClassGUI
@@ -373,12 +405,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
{
}
protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile
protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile
protected $m_aProfiles; // id -> object
protected $m_aUserProfiles = array(); // userid,profileid -> object
protected $m_aUserOrgs = array(); // userid -> array of orgid
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
@@ -426,114 +452,65 @@ class UserRightsProfile extends UserRightsAddOnAPI
return $this->m_aUserOrgs[$iUser];
}
/**
* Read and cache profiles of the given user
*/
protected function GetUserProfiles($iUser)
{
if (!array_key_exists($iUser, $this->m_aUserProfiles))
{
$oSearch = new DBObjectSearch('URP_UserProfile');
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$this->m_aUserProfiles[$iUser] = array();
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserProfile = $oUserProfileSet->Fetch())
{
$this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile;
}
}
return $this->m_aUserProfiles[$iUser];
}
public function ResetCache()
{
// Loaded by Load cache
$this->m_aProfiles = null;
$this->m_aUserProfiles = array();
$this->m_aUserOrgs = array();
$this->m_aAdmins = array();
$this->m_aPortalUsers = array();
// Cache
$this->m_aObjectActionGrants = array();
}
public function LoadCache()
{
if (!is_null($this->m_aProfiles)) return;
// Could be loaded in a shared memory (?)
$oKPI = new ExecutionKPI();
if (self::HasSharing())
static $bSharedObjectInitialized = false;
if (!$bSharedObjectInitialized)
{
SharedObject::InitSharedClassProperties();
$bSharedObjectInitialized = true;
if (self::HasSharing())
{
SharedObject::InitSharedClassProperties();
}
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
{
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)');
/*
echo "<pre>\n";
print_r($this->m_aProfiles);
print_r($this->m_aUserProfiles);
print_r($this->m_aUserOrgs);
echo "</pre>\n";
exit;
*/
return true;
}
/**
* @param $oUser User
* @return array
*/
public function IsAdministrator($oUser)
{
//$this->LoadCache();
$iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aAdmins))
{
$bIsAdmin = false;
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
{
if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME)
{
$bIsAdmin = true;
break;
}
}
$this->m_aAdmins[$iUser] = $bIsAdmin;
}
return $this->m_aAdmins[$iUser];
// UserRights caches the list for us
return UserRights::HasProfile(ADMIN_PROFILE_NAME, $oUser);
}
/**
* @param $oUser User
* @return array
*/
public function IsPortalUser($oUser)
{
//$this->LoadCache();
$iUser = $oUser->GetKey();
if (!array_key_exists($iUser, $this->m_aPortalUsers))
{
$bIsPortalUser = false;
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
{
if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME)
{
$bIsPortalUser = true;
break;
}
// UserRights caches the list for us
return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser);
}
$this->m_aPortalUsers[$iUser] = $bIsPortalUser;
/**
* @param $oUser User
* @return bool
*/
public function ListProfiles($oUser)
{
$aRet = array();
$oSearch = new DBObjectSearch('URP_UserProfile');
$oSearch->AllowAllData();
$oSearch->NoContextParameters();
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
$oProfiles = new DBObjectSet($oSearch);
while ($oUserProfile = $oProfiles->Fetch())
{
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
}
return $this->m_aPortalUsers[$iUser];
return $aRet;
}
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
@@ -563,72 +540,10 @@ exit;
return true;
}
$oExpression = new FieldExpression($sAttCode, $sClass);
$oFilter = new DBObjectSearch($sClass);
$oListExpr = ListExpression::FromScalars($aUserOrgs);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oFilter->AddConditionExpression($oCondition);
if (self::HasSharing())
{
if (($sAttCode == 'id') && isset($aSettings['bSearchMode']) && $aSettings['bSearchMode'])
{
// Querying organizations (or derived)
// and the expected list of organizations will be used as a search criteria
// Therefore the query can also return organization having objects shared with the allowed organizations
//
// 1) build the list of organizations sharing something with the allowed organizations
// Organization <== sharing_org_id == SharedObject having org_id IN {user orgs}
$oShareSearch = new DBObjectSearch('SharedObject');
$oOrgField = new FieldExpression('org_id', 'SharedObject');
$oShareSearch->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$oSearchSharers = new DBObjectSearch('Organization');
$oSearchSharers->AllowAllData();
$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
$aSharers = array();
foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
{
$aSharers[] = $aRow['id'];
}
// 2) Enlarge the overall results: ... OR id IN(id1, id2, id3)
if (count($aSharers) > 0)
{
$oSharersList = ListExpression::FromScalars($aSharers);
$oFilter->MergeConditionExpression(new BinaryExpression($oExpression, 'IN', $oSharersList));
}
}
$aShareProperties = SharedObject::GetSharedClassProperties($sClass);
if ($aShareProperties)
{
$sShareClass = $aShareProperties['share_class'];
$sShareAttCode = $aShareProperties['attcode'];
$oSearchShares = new DBObjectSearch($sShareClass);
$oSearchShares->AllowAllData();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
$oOrgField = new FieldExpression('org_id', $sShareClass);
$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$aShared = array();
foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
{
$aShared[] = $aRow[$sShareAttCode];
}
if (count($aShared) > 0)
{
$oObjId = new FieldExpression('id', $sClass);
$oSharedIdList = ListExpression::FromScalars($aShared);
$oFilter->MergeConditionExpression(new BinaryExpression($oObjId, 'IN', $oSharedIdList));
}
}
} // if HasSharing
return $oFilter;
return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode);
}
// This verb has been made public to allow the development of an accurate feedback for the current configuration
public function GetProfileActionGrant($iProfile, $sClass, $sAction)
{
@@ -651,8 +566,8 @@ exit;
$sAction = self::$m_aActionCodes[$iActionCode];
$bStatus = null;
$aAttributes = array();
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
// Call the API of UserRights because it caches the list for us
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
{
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
if (!is_null($bGrant))
@@ -675,12 +590,11 @@ exit;
$aRes = array(
'permission' => $iPermission,
// 'attributes' => $aAttributes,
);
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
return $aRes;
}
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
{
$this->LoadCache();
@@ -782,7 +696,8 @@ exit;
// Note: The object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set
$bStatus = null;
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
// Call the API of UserRights because it caches the list for us
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
{
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
if (!is_null($bGrant))

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -430,7 +430,7 @@ class URP_ActionGrant extends UserRightsBaseClass
//MetaModel::Init_InheritAttributes();
// Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked)
MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name")));
MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -467,7 +467,7 @@ class URP_StimulusGrant extends UserRightsBaseClass
//MetaModel::Init_InheritAttributes();
// Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked)
MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name")));
MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -503,7 +503,7 @@ class URP_AttributeGrant extends UserRightsBaseClass
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
@@ -822,70 +822,7 @@ exit;
return true;
}
$oExpression = new FieldExpression($sAttCode, $sClass);
$oFilter = new DBObjectSearch($sClass);
$oListExpr = ListExpression::FromScalars($aUserOrgs);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oFilter->AddConditionExpression($oCondition);
if (self::HasSharing())
{
if (($sAttCode == 'id') && isset($aSettings['bSearchMode']) && $aSettings['bSearchMode'])
{
// Querying organizations (or derived)
// and the expected list of organizations will be used as a search criteria
// Therefore the query can also return organization having objects shared with the allowed organizations
//
// 1) build the list of organizations sharing something with the allowed organizations
// Organization <== sharing_org_id == SharedObject having org_id IN {user orgs}
$oShareSearch = new DBObjectSearch('SharedObject');
$oOrgField = new FieldExpression('org_id', 'SharedObject');
$oShareSearch->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$oSearchSharers = new DBObjectSearch('Organization');
$oSearchSharers->AllowAllData();
$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
$aSharers = array();
foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
{
$aSharers[] = $aRow['id'];
}
// 2) Enlarge the overall results: ... OR id IN(id1, id2, id3)
if (count($aSharers) > 0)
{
$oSharersList = ListExpression::FromScalars($aSharers);
$oFilter->MergeConditionExpression(new BinaryExpression($oExpression, 'IN', $oSharersList));
}
}
$aShareProperties = SharedObject::GetSharedClassProperties($sClass);
if ($aShareProperties)
{
$sShareClass = $aShareProperties['share_class'];
$sShareAttCode = $aShareProperties['attcode'];
$oSearchShares = new DBObjectSearch($sShareClass);
$oSearchShares->AllowAllData();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
$oOrgField = new FieldExpression('org_id', $sShareClass);
$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$aShared = array();
foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
{
$aShared[] = $aRow[$sShareAttCode];
}
if (count($aShared) > 0)
{
$oObjId = new FieldExpression('id', $sClass);
$oSharedIdList = ListExpression::FromScalars($aShared);
$oFilter->MergeConditionExpression(new BinaryExpression($oObjId, 'IN', $oSharedIdList));
}
}
} // if HasSharing
return $oFilter;
return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode);
}
// This verb has been made public to allow the development of an accurate feedback for the current configuration

328
application/Html2Text.php Normal file
View File

@@ -0,0 +1,328 @@
<?php
namespace Html2Text;
if (!function_exists('mb_split'))
{
function mb_split($pattern, $subject, $limit = -1)
{
return preg_split('/'.$pattern.'/', $subject, $limit);
}
}
/**
* Replace all occurrences of the search string with the replacement string.
*
* @author Sean Murphy <sean@iamseanmurphy.com>
* @copyright Copyright 2012 Sean Murphy. All rights reserved.
* @license http://creativecommons.org/publicdomain/zero/1.0/
* @link http://php.net/manual/function.str-replace.php
*
* @param mixed $search
* @param mixed $replace
* @param mixed $subject
* @param int $count
* @return mixed
*/
function mb_str_replace($search, $replace, $subject, &$count = 0) {
if (!is_array($subject)) {
// Normalize $search and $replace so they are both arrays of the same length
$searches = is_array($search) ? array_values($search) : array($search);
$replacements = is_array($replace) ? array_values($replace) : array($replace);
$replacements = array_pad($replacements, count($searches), '');
foreach ($searches as $key => $search) {
$parts = mb_split(preg_quote($search), $subject);
$count += count($parts) - 1;
$subject = implode($replacements[$key], $parts);
}
} else {
// Call mb_str_replace for each subject in array, recursively
foreach ($subject as $key => $value) {
$subject[$key] = mb_str_replace($search, $replace, $value, $count);
}
}
return $subject;
}
/******************************************************************************
* Copyright (c) 2010 Jevon Wright and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* or
*
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
*
*
* Contributors:
* Jevon Wright - initial API and implementation
* Denis Flaven - some fixes for properly handling UTF-8 characters
****************************************************************************/
class Html2Text {
/**
* Tries to convert the given HTML into a plain text format - best suited for
* e-mail display, etc.
*
* <p>In particular, it tries to maintain the following features:
* <ul>
* <li>Links are maintained, with the 'href' copied over
* <li>Information in the &lt;head&gt; is lost
* </ul>
*
* @param string html the input HTML
* @return string the HTML converted, as best as possible, to text
* @throws Html2TextException if the HTML could not be loaded as a {@link DOMDocument}
*/
static function convert($html) {
// 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 = static::fixNewlines($html);
$doc = new \DOMDocument();
if (!@$doc->loadHTML('<?xml encoding="UTF-8">'.$html)) // Forces the UTF-8 character set for HTML fragments
{
throw new Html2TextException("Could not load HTML - badly formed?", $html);
}
$output = static::iterateOverNode($doc);
// remove leading and trailing spaces on each line
$output = preg_replace("/[ \t]*\n[ \t]*/im", "\n", $output);
$output = preg_replace("/ *\t */im", "\t", $output);
// remove unnecessary empty lines
$output = preg_replace("/\n\n\n*/im", "\n\n", $output);
// remove leading and trailing whitespace
$output = trim($output);
return $output;
}
/**
* Unify newlines; in particular, \r\n becomes \n, and
* then \r becomes \n. This means that all newlines (Unix, Windows, Mac)
* all become \ns.
*
* @param string text text with any number of \r, \r\n and \n combinations
* @return string the fixed text
*/
static function fixNewlines($text) {
// replace \r\n to \n
$text = str_replace("\r\n", "\n", $text);
// remove \rs
$text = str_replace("\r", "\n", $text);
return $text;
}
static function nextChildName($node) {
// get the next child
$nextNode = $node->nextSibling;
while ($nextNode != null) {
if ($nextNode instanceof \DOMElement) {
break;
}
$nextNode = $nextNode->nextSibling;
}
$nextName = null;
if ($nextNode instanceof \DOMElement && $nextNode != null) {
$nextName = strtolower($nextNode->nodeName);
}
return $nextName;
}
static function prevChildName($node) {
// get the previous child
$nextNode = $node->previousSibling;
while ($nextNode != null) {
if ($nextNode instanceof \DOMElement) {
break;
}
$nextNode = $nextNode->previousSibling;
}
$nextName = null;
if ($nextNode instanceof \DOMElement && $nextNode != null) {
$nextName = strtolower($nextNode->nodeName);
}
return $nextName;
}
static function iterateOverNode($node) {
if ($node instanceof \DOMText) {
// Replace whitespace characters with a space (equivilant to \s)
return preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $node->wholeText);
}
if ($node instanceof \DOMDocumentType) {
// ignore
return "";
}
$nextName = static::nextChildName($node);
$prevName = static::prevChildName($node);
$name = strtolower($node->nodeName);
// start whitespace
switch ($name) {
case "hr":
return "---------------------------------------------------------------\n";
case "style":
case "head":
case "title":
case "meta":
case "script":
// ignore these tags
return "";
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
case "ol":
case "ul":
// add two newlines, second line is added below
$output = "\n";
break;
case "td":
case "th":
// add tab char to separate table fields
$output = "\t";
break;
case "tr":
case "p":
case "div":
// add one line
$output = "\n";
break;
case "li":
$output = "- ";
break;
default:
// print out contents of unknown tags
$output = "";
break;
}
// debug
//$output .= "[$name,$nextName]";
if (isset($node->childNodes)) {
for ($i = 0; $i < $node->childNodes->length; $i++) {
$n = $node->childNodes->item($i);
$text = static::iterateOverNode($n);
$output .= $text;
}
}
// end whitespace
switch ($name) {
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
$output .= "\n";
break;
case "p":
case "br":
// add one line
if ($nextName != "div")
$output .= "\n";
break;
case "div":
// add one line only if the next child isn't a div
if ($nextName != "div" && $nextName != null)
$output .= "\n";
break;
case "a":
// links are returned in [text](link) format
$href = $node->getAttribute("href");
$output = trim($output);
// remove double [[ ]] s from linking images
if (substr($output, 0, 1) == "[" && substr($output, -1) == "]") {
$output = substr($output, 1, strlen($output) - 2);
// for linking images, the title of the <a> overrides the title of the <img>
if ($node->getAttribute("title")) {
$output = $node->getAttribute("title");
}
}
// if there is no link text, but a title attr
if (!$output && $node->getAttribute("title")) {
$output = $node->getAttribute("title");
}
if ($href == null) {
// it doesn't link anywhere
if ($node->getAttribute("name") != null) {
$output = "[$output]";
}
} else {
if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") {
// link to the same address: just use link
$output;
} else {
// replace it
if ($output) {
$output = "[$output]($href)";
} else {
// empty string
$output = $href;
}
}
}
// does the next node require additional whitespace?
switch ($nextName) {
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
$output .= "\n";
break;
}
break;
case "img":
if ($node->getAttribute("title")) {
$output = "[" . $node->getAttribute("title") . "]";
} elseif ($node->getAttribute("alt")) {
$output = "[" . $node->getAttribute("alt") . "]";
} else {
$output = "";
}
break;
case "li":
$output .= "\n";
break;
default:
// do nothing
}
return $output;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/******************************************************************************
* Copyright (c) 2010 Jevon Wright and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* or
*
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
*
*
* Contributors:
* Jevon Wright - initial API and implementation
****************************************************************************/
namespace Html2Text;
class Html2TextException extends \Exception {
var $more_info;
public function __construct($message = "", $more_info = "") {
parent::__construct($message);
$this->more_info = $more_info;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,22 +20,20 @@
* Simple web page with no includes, header or fancy formatting, useful to
* generate HTML fragments when called by an AJAX method
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT."/application/webpage.class.inc.php");
class ajax_page extends WebPage
class ajax_page extends WebPage implements iTabbedPage
{
/**
* Jquery style ready script
* @var Hash
*/
protected $m_sReadyScript;
protected $m_sCurrentTab;
protected $m_sCurrentTabContainer;
protected $m_aTabs;
protected $m_oTabs;
private $m_sMenu; // If set, then the menu will be updated
/**
@@ -44,13 +42,14 @@ class ajax_page extends WebPage
*/
function __construct($s_title)
{
parent::__construct($s_title);
$sPrintable = utils::ReadParam('printable', '0');
$bPrintable = ($sPrintable == '1');
parent::__construct($s_title, $bPrintable);
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header("Cache-control: no-cache");
$this->m_sCurrentTabContainer = '';
$this->m_sCurrentTab = '';
$this->m_aTabs = array();
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';
$this->m_sMenu = "";
@@ -58,41 +57,69 @@ class ajax_page extends WebPage
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->m_aTabs[$sTabContainer] = array('content' =>'', 'prefix' => $sPrefix);
$this->add("\$Tabs:$sTabContainer\$");
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
{
if (!isset($this->m_aTabs[$sTabContainer]['content'][$sTabLabel]))
{
// Set the content of the tab
$this->m_aTabs[$sTabContainer]['content'][$sTabLabel] = $sHtml;
}
else
{
// Append to the content of the tab
$this->m_aTabs[$sTabContainer]['content'][$sTabLabel] .= $sHtml;
}
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabLabel, $sHtml));
}
public function SetCurrentTabContainer($sTabContainer = '')
{
$sPreviousTabContainer = $this->m_sCurrentTabContainer;
$this->m_sCurrentTabContainer = $sTabContainer;
return $sPreviousTabContainer;
return $this->m_oTabs->SetCurrentTabContainer($sTabContainer);
}
public function SetCurrentTab($sTabLabel = '')
{
$sPreviousTab = $this->m_sCurrentTab;
$this->m_sCurrentTab = $sTabLabel;
return $sPreviousTab;
return $this->m_oTabs->SetCurrentTab($sTabLabel);
}
/**
* Add a tab which content will be loaded asynchronously via the supplied URL
*
* Limitations:
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
* Static content cannot be added inside such tabs.
*
* @param string $sTabLabel The (localised) label of the tab
* @param string $sUrl The URL to load (on the same server)
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
* @since 2.0.3
*/
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
{
$this->add($this->m_oTabs->AddAjaxTab($sTabLabel, $sUrl, $bCache));
}
public function GetCurrentTab()
{
return $this->m_sCurrentTab;
return $this->m_oTabs->GetCurrentTab();
}
public function RemoveTab($sTabLabel, $sTabContainer = null)
{
$this->m_oTabs->RemoveTab($sTabLabel, $sTabContainer);
}
/**
* Finds the tab whose title matches a given pattern
* @return mixed The name of the tab as a string or false if not found
*/
public function FindTab($sPattern, $sTabContainer = null)
{
return $this->m_oTabs->FindTab($sPattern, $sTabContainer);
}
/**
* Make the given tab the active one, as if it were clicked
* DOES NOT WORK: apparently in the *old* version of jquery
* that we are using this is not supported... TO DO upgrade
* the whole jquery bundle...
*/
public function SelectTab($sTabContainer, $sTabLabel)
{
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabLabel));
}
public function AddToMenu($sHtml)
@@ -118,13 +145,26 @@ class ajax_page extends WebPage
{
header($s_header);
}
if (count($this->m_aTabs) > 0)
if ($this->m_oTabs->TabsContainerCount() > 0)
{
$this->add_ready_script(
<<<EOF
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
// Ugly patch for a change in the behavior of jQuery UI:
// Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax)
// when their href was beginning by #. Starting with 1.9, a <base> tag in the page
// is taken into account and causes "local" tabs to be considered as Ajax
// unless their URL is equal to the URL of the page...
if ($('base').length > 0)
{
$('div[id^=tabbedContent] > ul > li > a').each(function() {
var sHash = location.hash;
var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, '');
$(this).attr("href", sCleanLocation+$(this).attr("href"));
});
}
if ($.bbq)
{
// This selector will be reused when selecting actual tab widget A elements.
@@ -160,36 +200,7 @@ EOF
);
}
// Render the tabs in the page (if any)
foreach($this->m_aTabs as $sTabContainerName => $aTabContainer)
{
$sTabs = '';
$m_aTabs = $aTabContainer['content'];
$sPrefix = $aTabContainer['prefix'];
$container_index = 0;
if (count($m_aTabs) > 0)
{
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$sTabContainerName}\" class=\"light\">\n";
$sTabs .= "<ul>\n";
// Display the unordered list that will be rendered as the tabs
$i = 0;
foreach($m_aTabs as $sTabName => $sTabContent)
{
$sTabs .= "<li><a href=\"#tab_{$sPrefix}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
$i++;
}
$sTabs .= "</ul>\n";
// Now add the content of the tabs themselves
$i = 0;
foreach($m_aTabs as $sTabName => $sTabContent)
{
$sTabs .= "<div id=\"tab_{$sPrefix}$i\">".$sTabContent."</div>\n";
$i++;
}
$sTabs .= "</div>\n<!-- end of tabs-->\n";
}
$this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content);
$container_index++;
}
$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)) )
@@ -217,9 +228,8 @@ EOF
EOF
);
}
$s_captured_output = ob_get_contents();
ob_end_clean();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
$s_captured_output = $this->ob_get_clean_safe();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
{
// inline content != attachment && html => filter all scripts for malicious XSS scripts
echo self::FilterXSS($this->s_content);
@@ -242,7 +252,6 @@ EOF
echo "<script type=\"text/javascript\">\n";
echo "$('#inner_menu').html($('#accordion_temp_$uid').html());\n";
echo "$('#accordion_temp_$uid').remove();\n";
echo "$('#accordion').accordion({ header: 'h3', navigation: true, autoHeight: false, collapsible: false, icons: false });\n";
echo "\n</script>\n";
}
@@ -271,9 +280,9 @@ EOF
echo self::FilterXSS($s_captured_output);
}
if (class_exists('MetaModel'))
if (class_exists('DBSearch'))
{
MetaModel::RecordQueryTrace();
DBSearch::RecordQueryTrace();
}
}
@@ -289,9 +298,9 @@ EOF
public function add($sHtml)
{
if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
{
$this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml);
$this->m_oTabs->AddToTab($this->m_oTabs->GetCurrentTabContainer(), $this->m_oTabs->GetCurrentTab(), $sHtml);
}
else
{
@@ -305,15 +314,18 @@ EOF
*/
public function start_capture()
{
if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
{
$iOffset = isset($this->m_aTabs[$this->m_sCurrentTabContainer]['content'][$this->m_sCurrentTab]) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer]['content'][$this->m_sCurrentTab]): 0;
return array('tc' => $this->m_sCurrentTabContainer, 'tab' => $this->m_sCurrentTab, 'offset' => $iOffset);
}
else
{
return parent::start_capture();
}
$sCurrentTabContainer = $this->m_oTabs->GetCurrentTabContainer();
$sCurrentTab = $this->m_oTabs->GetCurrentTab();
if (!empty($sCurrentTabContainer) && !empty($sCurrentTab))
{
$iOffset = $this->m_oTabs->GetCurrentTabLength();
return array('tc' => $sCurrentTabContainer, 'tab' => $sCurrentTab, 'offset' => $iOffset);
}
else
{
return parent::start_capture();
}
}
/**
@@ -324,23 +336,22 @@ EOF
*/
public function end_capture($offset)
{
if (is_array($offset))
{
if (isset($this->m_aTabs[$offset['tc']]['content'][$offset['tab']]))
{
$sCaptured = substr($this->m_aTabs[$offset['tc']]['content'][$offset['tab']], $offset['offset']);
$this->m_aTabs[$offset['tc']]['content'][$offset['tab']] = substr($this->m_aTabs[$offset['tc']]['content'][$offset['tab']], 0, $offset['offset']);
}
else
{
$sCaptured = '';
}
}
else
{
$sCaptured = parent::end_capture($offset);
}
return $sCaptured;
if (is_array($offset))
{
if ($this->m_oTabs->TabExists($offset['tc'], $offset['tab']))
{
$sCaptured = $this->m_oTabs->TruncateTab($offset['tc'], $offset['tab'], $offset['offset']);
}
else
{
$sCaptured = '';
}
}
else
{
$sCaptured = parent::end_capture($offset);
}
return $sCaptured;
}
/**
@@ -383,4 +394,3 @@ EOF
}
}
?>

View File

@@ -31,6 +31,7 @@ require_once(APPROOT.'/application/sqlblock.class.inc.php');
require_once(APPROOT.'/application/audit.category.class.inc.php');
require_once(APPROOT.'/application/audit.rule.class.inc.php');
require_once(APPROOT.'/application/query.class.inc.php');
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
//require_once(APPROOT.'/application/menunode.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -17,34 +17,248 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class iPlugin
* Management of application plugin
* Management of application plugins
*
* Definition of interfaces that can be implemented to customize iTop.
* You may implement such interfaces in a module file (e.g. main.mymodule.php)
*
* @package Extensibility
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @api
*/
/**
* Implement this interface to change the behavior of the GUI for some objects.
*
* All methods are invoked by iTop for a given object. There are basically two usages:
*
* 1) To tweak the form of an object, you will have to implement a specific behavior within:
*
* * OnDisplayProperties (bEditMode = true)
* * OnFormSubmit
* * OnFormCancel
*
* 2) To tune the display of the object details, you can use:
*
* * OnDisplayProperties
* * OnDisplayRelations
* * GetIcon
* * GetHilightClass
*
* Please note that some of the APIs can be called several times for a single page displayed.
* Therefore it is not recommended to perform too many operations, such as querying the database.
* A recommended pattern is to cache data by the mean of static members.
*
* @package Extensibility
* @api
*/
interface iApplicationUIExtension
{
/**
* Invoked when an object is being displayed (wiew or edit)
*
* The method is called right after the main tab has been displayed.
* You can add output to the page, either to change the display, or to add a form input
*
* Example:
* <code>
* if ($bEditMode)
* {
* $oPage->p('Age of the captain: &lt;input type="text" name="captain_age"/&gt;');
* }
* else
* {
* $oPage->p('Age of the captain: '.$iCaptainAge);
* }
* </code>
*
* @param DBObject $oObject The object being displayed
* @param WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
* @return void
*/
public function OnDisplayProperties($oObject, WebPage $oPage, $bEditMode = false);
public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false);
public function OnFormSubmit($oObject, $sFormPrefix = '');
public function OnFormCancel($sTempId); // temp id is made of session_id and transaction_id, it identifies the object in a unique way
/**
* Invoked when an object is being displayed (wiew or edit)
*
* The method is called rigth after all the tabs have been displayed
*
* @param DBObject $oObject The object being displayed
* @param WebPage $oPage The output context
* @param boolean $bEditMode True if the edition form is being displayed
* @return void
*/
public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false);
/**
* Invoked when the end-user clicks on Modify from the object edition form
*
* The method is called after the changes from the standard form have been
* taken into account, and before saving the changes into the database.
*
* @param DBObject $oObject The object being edited
* @param string $sFormPrefix Prefix given to the HTML form inputs
* @return void
*/
public function OnFormSubmit($oObject, $sFormPrefix = '');
/**
* Invoked when the end-user clicks on Cancel from the object edition form
*
* Implement here any cleanup. This is necessary when you have injected some
* javascript into the edition form, and if that code requires to store temporary data
* (this is the case when a file must be uploaded).
*
* @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way.
* @return void
*/
public function OnFormCancel($sTempId);
/**
* Not yet called by the framework!
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @param DBObject $oObject The object being displayed
* @return type desc
*/
public function EnumUsedAttributes($oObject); // Not yet implemented
/**
* Not yet called by the framework!
*
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
*
* @param DBObject $oObject The object being displayed
* @return string Path of the icon, relative to the modules directory.
*/
public function GetIcon($oObject); // Not yet implemented
/**
* Invoked when the object is displayed alone or within a list
*
* Returns a value influencing the appearance of the object depending on its
* state.
*
* Possible values are:
*
* * HILIGHT_CLASS_CRITICAL
* * HILIGHT_CLASS_WARNING
* * HILIGHT_CLASS_OK
* * HILIGHT_CLASS_NONE
*
* @param DBObject $oObject The object being displayed
* @return integer The value representing the mood of the object
*/
public function GetHilightClass($oObject);
/**
* Called when building the Actions menu for a single object or a list of objects
*
* Use this to add items to the Actions menu. You will have to specify a label and an URL.
*
* Example:
* <code>
* $oObject = $oSet->fetch();
* if ($oObject instanceof Sheep)
* {
* return array('View in my app' => 'http://myserver/view_sheeps?id='.$oObject->Get('name'));
* }
* else
* {
* return array();
* }
* </code>
*
* See also iPopupMenuExtension for greater flexibility
*
* @param DBObjectSet $oSet A set of persistent objects (DBObject)
* @return string[string]
*/
public function EnumAllowedActions(DBObjectSet $oSet);
}
/**
* Implement this interface to perform specific things when objects are manipulated
*
* Note that those methods will be called when objects are manipulated, either in a programmatic way
* or through the GUI.
*
* @package Extensibility
* @api
*/
interface iApplicationObjectExtension
{
/**
* Invoked to determine wether an object has been modified in memory
*
* The GUI calls this verb to determine the message that will be displayed to the end-user.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
*
* If the extension returns false, then the framework will perform the usual evaluation.
* Otherwise, the answer is definitively "yes, the object has changed".
*
* @param DBObject $oObject The target object
* @return boolean True if something has changed for the target object
*/
public function OnIsModified($oObject);
/**
* Invoked to determine wether an object can be written to the database
*
* The GUI calls this verb and reports any issue.
* Anyhow, this API can be called in other contexts such as the CSV import tool.
*
* @param DBObject $oObject The target object
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
*/
public function OnCheckToWrite($oObject);
/**
* Invoked to determine wether an object can be deleted from the database
*
* The GUI calls this verb and stops the deletion process if any issue is reported.
*
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
*
* @param DBObject $oObject The target object
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
*/
public function OnCheckToDelete($oObject);
/**
* Invoked when an object is updated into the database
*
* The method is called right <b>after</b> the object has been written to the database.
*
* @param DBObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page
* @return void
*/
public function OnDBUpdate($oObject, $oChange = null);
/**
* Invoked when an object is created into the database
*
* The method is called right <b>after</b> the object has been written to the database.
*
* @param DBObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page
* @return void
*/
public function OnDBInsert($oObject, $oChange = null);
/**
* Invoked when an object is deleted from the database
*
* The method is called right <b>before</b> the object will be deleted from the database.
*
* @param DBObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information once for all the changes made within the current page
* @return void
*/
public function OnDBDelete($oObject, $oChange = null);
}
@@ -54,46 +268,102 @@ interface iApplicationObjectExtension
*
* To add some menus into iTop, declare a class that implements this interface, it will be called automatically
* by the application, as long as the class definition is included somewhere in the code
*
* @package Extensibility
* @api
* @since 2.0
*/
interface iPopupMenuExtension
{
// Possible types of menu into which new items can be added
const MENU_OBJLIST_ACTIONS = 1; // $param is a DBObjectSet containing the list of objects
const MENU_OBJLIST_TOOLKIT = 2; // $param is a DBObjectSet containing the list of objects
const MENU_OBJDETAILS_ACTIONS = 3; // $param is a DBObject instance: the object currently displayed
const MENU_DASHBOARD_ACTIONS = 4; // $param is a Dashboard instance: the dashboard currently displayed
const MENU_USER_ACTIONS = 5; // $param is a null ??
/**
* Insert an item into the Actions menu of a list
*
* $param is a DBObjectSet containing the list of objects
*/
const MENU_OBJLIST_ACTIONS = 1;
/**
* Insert an item into the Toolkit menu of a list
*
* $param is a DBObjectSet containing the list of objects
*/
const MENU_OBJLIST_TOOLKIT = 2;
/**
* Insert an item into the Actions menu on an object details page
*
* $param is a DBObject instance: the object currently displayed
*/
const MENU_OBJDETAILS_ACTIONS = 3;
/**
* Insert an item into the Dashboard menu
*
* The dashboad menu is shown on the top right corner when a dashboard
* is being displayed.
*
* $param is a Dashboard instance: the dashboard currently displayed
*/
const MENU_DASHBOARD_ACTIONS = 4;
/**
* Insert an item into the User menu (upper right corner)
*
* $param is null
*/
const MENU_USER_ACTIONS = 5;
/**
* Get the list of items to be added to a menu. The items will be inserted in the menu in the order of the returned array
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx above
* Get the list of items to be added to a menu.
*
* This method is called by the framework for each menu.
* The items will be inserted in the menu in the order of the returned array.
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx
* @param mixed $param Depends on $iMenuId, see the constants defined above
* @return Array An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu
* @return object[] An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu
*/
public static function EnumItems($iMenuId, $param);
}
/**
* Each menu items is defined by an instance of an object derived from the class
* ApplicationPopupMenu below
*
* Base class for the various types of custom menus
*
* @package Extensibility
* @internal
* @since 2.0
*/
abstract class ApplicationPopupMenuItem
{
/** @ignore */
protected $sUID;
/** @ignore */
protected $sLabel;
/**
* Constructor
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
*/
public function __construct($sUID, $sLabel)
{
$this->sUID = $sUID;
$this->sLabel = $sLabel;
}
/**
* Get the UID
*
* @return string The unique identifier
* @ignore
*/
public function GetUID()
{
return $this->sUID;
}
/**
* Get the label
*
* @return string The label
* @ignore
*/
public function GetLabel()
{
return $this->sLabel;
@@ -102,9 +372,11 @@ abstract class ApplicationPopupMenuItem
/**
* Returns the components to create a popup menu item in HTML
* @return Hash A hash array: array('label' => , 'url' => , 'target' => , 'onclick' => )
* @ignore
*/
abstract public function GetMenuItem();
/** @ignore */
public function GetLinkedScripts()
{
return array();
@@ -113,14 +385,21 @@ abstract class ApplicationPopupMenuItem
/**
* Class for adding an item into a popup menu that browses to the given URL
*
* @package Extensibility
* @api
* @since 2.0
*/
class URLPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sURL;
/** @ignore */
protected $sTarget;
/**
* Class for adding an item that browses to the given URL
* Constructor
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
@@ -133,6 +412,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
$this->sTarget = $sTarget;
}
/** @ignore */
public function GetMenuItem()
{
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget);
@@ -141,10 +421,16 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/**
* Class for adding an item into a popup menu that triggers some Javascript code
*
* @package Extensibility
* @api
* @since 2.0
*/
class JSPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sJSCode;
/** @ignore */
protected $aIncludeJSFiles;
/**
@@ -161,11 +447,14 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
$this->aIncludeJSFiles = $aIncludeJSFiles;
}
/** @ignore */
public function GetMenuItem()
{
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode, 'url' => '#');
// Note: the semicolumn is a must here!
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#');
}
/** @ignore */
public function GetLinkedScripts()
{
return $this->aIncludeJSFiles;
@@ -175,17 +464,23 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
/**
* Class for adding a separator (horizontal line, not selectable) the output
* will automatically reduce several consecutive separators to just one
*
* @package Extensibility
* @api
* @since 2.0
*/
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
{
static $idx = 0;
/**
* Class for inserting a separator into a popup menu
* Constructor
*/
public function __construct()
{
parent::__construct('', '');
parent::__construct('_separator_'.(self::$idx++), '');
}
/** @ignore */
public function GetMenuItem()
{
return array ('label' => '<hr class="menu-separator">', 'url' => '');
@@ -194,34 +489,40 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
/**
* Implement this interface to add content to any iTopWebPage
*
* There are 3 places where content can be added:
* - The north pane: (normaly empty/hidden) at the top of the page, spanning the whole
*
* * The north pane: (normaly empty/hidden) at the top of the page, spanning the whole
* width of the page
* - The south pane: (normaly empty/hidden) at the bottom of the page, spanning the whole
* * The south pane: (normaly empty/hidden) at the bottom of the page, spanning the whole
* width of the page
* - The admin banner (two tones gray background) at the left of the global search.
* * The admin banner (two tones gray background) at the left of the global search.
* Limited space, use it for short messages
*
* Each of the methods of this interface is supposed to return the HTML to be inserted at
* the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions
*
* @package Extensibility
* @api
* @since 2.0
*/
interface iPageUIExtension
{
/**
* Add content to the North pane
* @param WebPage $oPage The page to insert stuff into.
* @param iTopWebPage $oPage The page to insert stuff into.
* @return string The HTML content to add into the page
*/
public function GetNorthPaneHtml(iTopWebPage $oPage);
/**
* Add content to the South pane
* @param WebPage $oPage The page to insert stuff into.
* @param iTopWebPage $oPage The page to insert stuff into.
* @return string The HTML content to add into the page
*/
public function GetSouthPaneHtml(iTopWebPage $oPage);
/**
* Add content to the "admin banner"
* @param WebPage $oPage The page to insert stuff into.
* @param iTopWebPage $oPage The page to insert stuff into.
* @return string The HTML content to add into the page
*/
public function GetBannerHtml(iTopWebPage $oPage);
@@ -280,6 +581,14 @@ class RestResult
* Result: the input structure is not a valid JSON string
*/
const INVALID_JSON = 4;
/**
* Result: the parameter 'auth_user' is missing, authentication aborted
*/
const MISSING_AUTH_USER = 5;
/**
* Result: the parameter 'auth_pwd' is missing, authentication aborted
*/
const MISSING_AUTH_PWD = 6;
/**
* Result: no operation is available for the specified version
*/
@@ -406,7 +715,7 @@ class RestUtils
* @param string $sClass Name of the class
* @param StdClass $oData Structured input data.
* @param string $sParamName Name of the parameter to fetch from the input data
* @return void
* @return An array of class => list of attributes (see RestResultWithObjects::AddObject that uses it)
* @throws Exception
* @api
*/
@@ -418,7 +727,17 @@ class RestUtils
{
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
$aShowFields[] = $sAttCode;
$aShowFields[$sClass][] = $sAttCode;
}
}
elseif ($sFields == '*+')
{
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass)
{
foreach (MetaModel::ListAttributeDefs($sRefClass) as $sAttCode => $oAttDef)
{
$aShowFields[$sRefClass][] = $sAttCode;
}
}
}
else
@@ -430,7 +749,7 @@ class RestUtils
{
throw new Exception("$sParamName: invalid attribute code '$sAttCode'");
}
$aShowFields[] = $sAttCode;
$aShowFields[$sClass][] = $sAttCode;
}
}
return $aShowFields;
@@ -449,17 +768,25 @@ class RestUtils
$aCriteriaReport = array();
if (isset($oCriteria->finalclass))
{
$sClass = $oCriteria->finalclass;
if (!MetaModel::IsValidClass($sClass))
if (!MetaModel::IsValidClass($oCriteria->finalclass))
{
throw new Exception("finalclass: Unknown class '$sClass'");
throw new Exception("finalclass: Unknown class '".$oCriteria->finalclass."'");
}
if (!MetaModel::IsParentClass($sClass, $oCriteria->finalclass))
{
throw new Exception("finalclass: '".$oCriteria->finalclass."' is not a child class of '$sClass'");
}
$sClass = $oCriteria->finalclass;
}
$oSearch = new DBObjectSearch($sClass);
foreach ($oCriteria as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
if (is_object($value) || is_array($value))
{
$value = json_encode($value);
}
$aCriteriaReport[] = "$sAttCode: $value ($realValue)";
}
$oSet = new DBObjectSet($oSearch);
@@ -482,22 +809,30 @@ class RestUtils
*
* @param string $sClass Name of the class
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
* @param bool $bAllowNullValue Allow the cases such as key = 0 or key = {null} and return null then
* @return DBObject The object found
* @throws Exception If the input structure is not valid or it could not find exactly one object
* @api
*/
public static function FindObjectFromKey($sClass, $key)
public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false)
{
if (is_object($key))
{
$res = self::FindObjectFromCriteria($sClass, $key);
$res = static::FindObjectFromCriteria($sClass, $key);
}
elseif (is_numeric($key))
{
$res = MetaModel::GetObject($sClass, $key, false);
if (is_null($res))
if ($bAllowNullValue && ($key == 0))
{
throw new Exception("Invalid object $sClass::$key");
$res = null;
}
else
{
$res = MetaModel::GetObject($sClass, $key, false);
if (is_null($res))
{
throw new Exception("Invalid object $sClass::$key");
}
}
}
elseif (is_string($key))
@@ -547,8 +882,8 @@ class RestUtils
$oSearch = new DBObjectSearch($sClass);
foreach ($key as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
}
}
elseif (is_numeric($key))
@@ -591,8 +926,8 @@ class RestUtils
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey)
{
$oExtKeyObject = self::FindObjectFromKey($oAttDef->GetTargetClass(), $value);
$value = $oExtKeyObject->GetKey();
$oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0;
}
elseif ($oAttDef instanceof AttributeLinkedSet)
{
@@ -604,7 +939,7 @@ class RestUtils
$aLinks = array();
foreach($value as $oValues)
{
$oLnk = self::MakeObjectFromFields($sLnkClass, $oValues);
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
$aLinks[] = $oLnk;
}
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
@@ -635,8 +970,15 @@ class RestUtils
$oObject = MetaModel::NewObject($sClass);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$oObject->Set($sAttCode, $realValue);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);
}
catch (Exception $e)
{
throw new Exception("$sAttCode: ".$e->getMessage(), $e->getCode());
}
}
return $oObject;
}
@@ -655,8 +997,15 @@ class RestUtils
$sClass = get_class($oObject);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$oObject->Set($sAttCode, $realValue);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);
}
catch (Exception $e)
{
throw new Exception("$sAttCode: ".$e->getMessage(), $e->getCode());
}
}
return $oObject;
}

View File

@@ -47,7 +47,7 @@ class AuditCategory extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE)));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL)));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details

View File

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

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* CLI page
* The page adds the content-type text/XML and the encoding into the headers
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -35,9 +35,13 @@ class CLIPage implements Page
public function output()
{
if (class_exists('MetaModel'))
if (class_exists('DBSearch'))
{
MetaModel::RecordQueryTrace();
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* Simple web page with no includes or fancy formatting, useful to generateXML documents
* The page adds the content-type text/XML and the encoding into the headers
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -39,7 +39,11 @@ class CSVPage extends WebPage
public function output()
{
$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
@@ -47,9 +51,13 @@ class CSVPage extends WebPage
echo trim($this->s_content);
echo "\n";
if (class_exists('MetaModel'))
if (class_exists('DBSearch'))
{
MetaModel::RecordQueryTrace();
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}
@@ -101,4 +109,3 @@ class CSVPage extends WebPage
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,6 +18,7 @@
require_once(APPROOT.'application/dashboardlayout.class.inc.php');
require_once(APPROOT.'application/dashlet.class.inc.php');
require_once(APPROOT.'core/modelreflection.class.inc.php');
/**
* A user editable dashboard page
@@ -28,20 +29,26 @@ require_once(APPROOT.'application/dashlet.class.inc.php');
abstract class Dashboard
{
protected $sTitle;
protected $bAutoReload;
protected $iAutoReloadSec;
protected $sLayoutClass;
protected $aWidgetsData;
protected $oDOMNode;
protected $sId;
protected $aCells;
protected $oMetaModel;
public function __construct($sId)
{
$this->sLayoutClass = null;
$this->sTitle = '';
$this->sLayoutClass = 'DashboardLayoutOneCol';
$this->bAutoReload = false;
$this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval();
$this->aCells = array();
$this->oDOMNode = null;
$this->sId = $sId;
}
public function FromXml($sXml)
{
$this->aCells = array(); // reset the content of the dashboard
@@ -49,60 +56,97 @@ abstract class Dashboard
$oDoc = new DOMDocument();
$oDoc->loadXML($sXml);
restore_error_handler();
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
$oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0);
$this->sLayoutClass = $oLayoutNode->textContent;
$oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0);
$this->sTitle = $oTitleNode->textContent;
$oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0);
$oCellsList = $oCellsNode->getElementsByTagName('cell');
$aCellOrder = array();
$iCellRank = 0;
foreach($oCellsList as $oCellNode)
{
$aDashletList = array();
$oCellRank = $oCellNode->getElementsByTagName('rank')->item(0);
if ($oCellRank)
{
$iCellRank = (float)$oCellRank->textContent;
}
$oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0);
$oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
$iRank = 0;
$aDashletOrder = array();
foreach($oDashletList as $oDomNode)
{
$sDashletClass = $oDomNode->getAttribute('xsi:type');
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank)
{
$iRank = (float)$oRank->textContent;
}
$sId = $oDomNode->getAttribute('id');
$oNewDashlet = new $sDashletClass($sId);
$oNewDashlet->FromDOMNode($oDomNode);
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
}
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
$aDashletList = array();
foreach($aDashletOrder as $aItem)
{
$aDashletList[] = $aItem['dashlet'];
}
$aCellOrder[] = array('rank' => $iCellRank, 'dashlets' => $aDashletList);
}
usort($aCellOrder, array(get_class($this), 'SortOnRank'));
foreach($aCellOrder as $aItem)
{
$this->aCells[] = $aItem['dashlets'];
}
$this->FromDOMDocument($oDoc);
}
public function FromDOMDocument(DOMDocument $oDoc)
{
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0))
{
$this->sLayoutClass = $oLayoutNode->textContent;
}
else
{
$this->sLayoutClass = 'DashboardLayoutOneCol';
}
if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0))
{
$this->sTitle = $oTitleNode->textContent;
}
else
{
$this->sTitle = '';
}
$this->bAutoReload = false;
$this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval();
if ($oAutoReloadNode = $this->oDOMNode->getElementsByTagName('auto_reload')->item(0))
{
if ($oAutoReloadEnabled = $oAutoReloadNode->getElementsByTagName('enabled')->item(0))
{
$this->bAutoReload = ($oAutoReloadEnabled->textContent == 'true');
}
if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0))
{
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent);
}
}
if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0))
{
$oCellsList = $oCellsNode->getElementsByTagName('cell');
$aCellOrder = array();
$iCellRank = 0;
foreach($oCellsList as $oCellNode)
{
$aDashletList = array();
$oCellRank = $oCellNode->getElementsByTagName('rank')->item(0);
if ($oCellRank)
{
$iCellRank = (float)$oCellRank->textContent;
}
$oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0);
{
$oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
$iRank = 0;
$aDashletOrder = array();
foreach($oDashletList as $oDomNode)
{
$sDashletClass = $oDomNode->getAttribute('xsi:type');
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank)
{
$iRank = (float)$oRank->textContent;
}
$sId = $oDomNode->getAttribute('id');
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
$oNewDashlet->FromDOMNode($oDomNode);
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
}
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
$aDashletList = array();
foreach($aDashletOrder as $aItem)
{
$aDashletList[] = $aItem['dashlet'];
}
$aCellOrder[] = array('rank' => $iCellRank, 'dashlets' => $aDashletList);
}
}
usort($aCellOrder, array(get_class($this), 'SortOnRank'));
foreach($aCellOrder as $aItem)
{
$this->aCells[] = $aItem['dashlets'];
}
}
else
{
$this->aCells = array();
}
}
static function SortOnRank($aItem1, $aItem2)
{
return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1;
@@ -132,14 +176,31 @@ abstract class Dashboard
$oMainNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
$oDoc->appendChild($oMainNode);
$this->ToDOMNode($oMainNode);
$sXml = $oDoc->saveXML();
return $sXml;
}
public function ToDOMNode($oDefinition)
{
$oDoc = $oDefinition->ownerDocument;
$oNode = $oDoc->createElement('layout', $this->sLayoutClass);
$oMainNode->appendChild($oNode);
$oDefinition->appendChild($oNode);
$oNode = $oDoc->createElement('title', $this->sTitle);
$oMainNode->appendChild($oNode);
$oDefinition->appendChild($oNode);
$oAutoReloadNode = $oDoc->createElement('auto_reload');
$oDefinition->appendChild($oAutoReloadNode);
$oNode = $oDoc->createElement('enabled', $this->bAutoReload ? 'true' : 'false');
$oAutoReloadNode->appendChild($oNode);
$oNode = $oDoc->createElement('interval', $this->iAutoReloadSec);
$oAutoReloadNode->appendChild($oNode);
$oCellsNode = $oDoc->createElement('cells');
$oMainNode->appendChild($oCellsNode);
$oDefinition->appendChild($oCellsNode);
$iCellRank = 0;
foreach ($this->aCells as $aCell)
@@ -166,15 +227,15 @@ abstract class Dashboard
$oDashlet->ToDOMNode($oNode);
}
}
$sXml = $oDoc->saveXML();
return $sXml;
}
public function FromParams($aParams)
{
$this->sLayoutClass = $aParams['layout_class'];
$this->sTitle = $aParams['title'];
$this->bAutoReload = $aParams['auto_reload'] == 'true';
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int) $aParams['auto_reload_sec']);
foreach($aParams['cells'] as $aCell)
{
@@ -183,7 +244,7 @@ abstract class Dashboard
{
$sDashletClass = $aDashletParams['dashlet_class'];
$sId = $aDashletParams['dashlet_id'];
$oNewDashlet = new $sDashletClass($sId);
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
$oForm = $oNewDashlet->GetForm();
$oForm->SetParamsContainer($sId);
@@ -196,7 +257,7 @@ abstract class Dashboard
}
}
public function Save()
{
@@ -216,12 +277,32 @@ abstract class Dashboard
{
return $this->sTitle;
}
public function SetTitle($sTitle)
{
$this->sTitle = $sTitle;
}
public function GetAutoReload()
{
return $this->bAutoReload;
}
public function SetAutoReload($bAutoReload)
{
$this->bAutoReload = $bAutoReload;
}
public function GetAutoReloadInterval()
{
return $this->iAutoReloadSec;
}
public function SetAutoReloadInterval($iAutoReloadSec)
{
$this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$iAutoReloadSec);
}
public function AddDashlet($oDashlet)
{
$sId = $this->GetNewDashletId();
@@ -231,7 +312,7 @@ abstract class Dashboard
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
$oPage->add('<h1>'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</h1>');
$oLayout = new $this->sLayoutClass;
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
if (!$bEditMode)
@@ -266,24 +347,62 @@ abstract class Dashboard
$oPage->add('</div>');
$oForm = new DesignerForm();
$oField = new DesignerHiddenField('dashboard_id', '', $this->sId);
$oForm->AddField($oField);
$oField = new DesignerLongTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle);
$oForm->AddField($oField);
$oField = new DesignerBooleanField('auto_reload', Dict::S('UI:DashboardEdit:AutoReload'), $this->bAutoReload);
$oForm->AddField($oField);
$oField = new DesignerIntegerField('auto_reload_sec', Dict::S('UI:DashboardEdit:AutoReloadSec'), $this->iAutoReloadSec);
$oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit
$oForm->AddField($oField);
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
$sRateTitle = addslashes(Dict::Format('UI:DashboardEdit:AutoReloadSec+', MetaModel::GetConfig()->Get('min_reload_interval')));
$oPage->add_ready_script(
<<<EOF
$('#select_layout').buttonset();
$('#select_layout input').click( function() {
var sLayoutClass = $(this).val();
$(':itop-dashboard').dashboard('option', {layout_class: sLayoutClass});
// Note: the title gets deleted by the validation mechanism
$("#attr_auto_reload_sec").tooltip({items: 'input', content: '$sRateTitle'});
$("#attr_auto_reload_sec").prop('disabled', !$('#attr_auto_reload').is(':checked'));
$('#attr_auto_reload').change( function(ev) {
$("#attr_auto_reload_sec").prop('disabled', !$(this).is(':checked'));
} );
$('#row_attr_dashboard_title').property_field('option', {parent_selector: ':itop-dashboard', auto_apply: false, 'do_apply': function() {
var sTitle = $('#attr_dashboard_title').val();
$(':itop-dashboard').dashboard('option', {title: sTitle});
return true;
}
$('#select_layout').buttonset();
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
$( this ).find( ".placeholder" ).remove();
var oDashlet = ui.draggable.data('itopDashlet');
oDashlet._remove_dashlet();
},
});
$('#event_bus').bind('dashlet-selected', function(event, data){
var sDashletId = data.dashlet_id;
var sPropId = 'dashlet_properties_'+sDashletId;
$('.dashlet_properties').each(function() {
var sId = $(this).attr('id');
var bShow = (sId == sPropId);
if (bShow)
{
$(this).show();
}
else
{
$(this).hide();
}
});
});
EOF
);
@@ -318,7 +437,6 @@ EOF
$oPage->add('</div>');
$oPage->add_ready_script("$('.dashlet_icon').draggable({helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});");
$oPage->add_ready_script("$('.layout_cell').droppable({accept:'.dashlet_icon', hoverClass:'dragHover'});");
}
public function RenderDashletsProperties($oPage)
@@ -338,7 +456,7 @@ EOF
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
$oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
}
}
@@ -367,11 +485,12 @@ EOF
class RuntimeDashboard extends Dashboard
{
protected $bCustomized;
public function __construct($sId)
{
parent::__construct($sId);
$this->bCustomized = false;
$this->oMetaModel = new ModelReflectionRuntime();
}
public function SetCustomFlag($bCustomized)
@@ -425,37 +544,36 @@ class RuntimeDashboard extends Dashboard
}
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
public function RenderEditionTools($oPage)
{
parent::Render($oPage, $bEditMode, $aExtraParams);
if (!$bEditMode)
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/pencil-menu.png\"><ul>";
$aActions = array();
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}')");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
if ($this->bCustomized)
{
$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/edit.png\"><ul>";
$aActions = array();
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}')");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
$oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'),
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}'); else return false");
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
}
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
$sEditMenu .= $oPage->RenderPopupMenuItems($aActions);
if ($this->bCustomized)
{
$oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'),
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}'); else return false");
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
}
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
$sEditMenu .= $oPage->RenderPopupMenuItems($aActions);
$sEditMenu = addslashes($sEditMenu);
//$sEditBtn = addslashes('<div style="display: inline-block; height: 55px; width:200px;vertical-align:center;line-height:60px;text-align:left;"><button onclick="EditDashboard(\''.$this->sId.'\');">Edit This Page</button></div>');
$oPage->add_ready_script(
$sEditMenu = addslashes($sEditMenu);
//$sEditBtn = addslashes('<div style="display: inline-block; height: 55px; width:200px;vertical-align:center;line-height:60px;text-align:left;"><button onclick="EditDashboard(\''.$this->sId.'\');">Edit This Page</button></div>');
$oPage->add_ready_script(
<<<EOF
$('#logOffBtn').parent().before('$sEditMenu');
$('#DashboardMenu>ul').popupmenu();
EOF
);
$oPage->add_script(
);
$oPage->add_script(
<<<EOF
function EditDashboard(sId)
{
@@ -478,10 +596,42 @@ function RevertDashboard(sId)
return false;
}
EOF
);
}
);
}
public function RenderProperties($oPage)
{
parent::RenderProperties($oPage);
$oPage->add_ready_script(
<<<EOF
$('#select_layout input').click( function() {
var sLayoutClass = $(this).val();
$('.itop-dashboard').runtimedashboard('option', {layout_class: sLayoutClass});
} );
$('#row_attr_dashboard_title').property_field('option', {parent_selector: '.itop-dashboard', auto_apply: false, 'do_apply': function() {
var sTitle = $('#attr_dashboard_title').val();
$('.itop-dashboard').runtimedashboard('option', {title: sTitle});
return true;
}
});
$('#row_attr_auto_reload').property_field('option', {parent_selector: '.itop-dashboard', auto_apply: true, 'do_apply': function() {
var bAutoReload = $('#attr_auto_reload').is(':checked');
$('.itop-dashboard').runtimedashboard('option', {auto_reload: bAutoReload});
return true;
}
});
$('#row_attr_auto_reload_sec').property_field('option', {parent_selector: '.itop-dashboard', auto_apply: true, 'do_apply': function() {
var iAutoReloadSec = $('#attr_auto_reload_sec').val();
$('.itop-dashboard').runtimedashboard('option', {auto_reload_sec: iAutoReloadSec});
return true;
}
});
EOF
);
}
public function RenderEditor($oPage)
{
$oPage->add('<div id="dashboard_editor">');
@@ -502,6 +652,8 @@ EOF
$sId = addslashes($this->sId);
$sLayoutClass = addslashes($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = addslashes($this->sTitle);
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
@@ -520,7 +672,7 @@ $('#dashboard_editor').dialog({
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('dashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard.is_dirty())
{
if (!confirm('$sAutoApplyConfirmationMessage'))
@@ -536,7 +688,7 @@ $('#dashboard_editor').dialog({
oDashboard.save();
} },
{ text: "$sCancelButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('dashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard.is_modified())
{
if (!confirm('$sCancelConfirmationMessage'))
@@ -552,40 +704,14 @@ $('#dashboard_editor').dialog({
close: function() { $(this).remove(); }
});
$('#dashboard_editor .ui-layout-center').dashboard({
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId', layout_class: '$sLayoutClass', title: '$sTitle',
auto_reload: $sAutoReload, auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl', submit_parameters: {operation: 'save_dashboard'},
render_to: '$sUrl', render_parameters: {operation: 'render_dashboard'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
$( this ).find( ".placeholder" ).remove();
var oDashlet = ui.draggable;
oDashlet.remove();
},
});
$('#event_bus').bind('dashlet-selected', function(event, data){
var sDashletId = data.dashlet_id;
var sPropId = 'dashlet_properties_'+sDashletId;
$('.dashlet_properties').each(function() {
var sId = $(this).attr('id');
var bShow = (sId == sPropId);
if (bShow)
{
$(this).show();
}
else
{
$(this).hide();
}
});
});
dashboard_prop_size = GetUserPreference('dashboard_prop_size', 350);
$('#dashboard_editor').layout({
east: {
@@ -606,7 +732,7 @@ $('#dashboard_editor').layout({
window.onbeforeunload = function() {
if (!window.bLeavingOnUserAction)
{
var oDashboard = $(':itop-dashboard').data('dashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard)
{
if (oDashboard.is_dirty())
@@ -687,7 +813,8 @@ EOF
foreach($aDashlets as $sDashletClass => $aDashletInfo)
{
$oSubForm = new DesignerForm();
$oDashlet = new $sDashletClass(0);
$oMetaModel = new ModelReflectionRuntime();
$oDashlet = new $sDashletClass($oMetaModel, 0);
$oDashlet->GetPropertiesFieldsFromOQL($oSubForm, $sOQL);
$oSelectorField->AddSubForm($oSubForm, $aDashletInfo['label'], $aDashletInfo['class']);

View File

@@ -107,14 +107,16 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
$oPage->add('<table style="width:100%"><tbody>');
$iCellIdx = 0;
$fColSize = 100 / $this->iNbCols;
$sStyle = $bEditMode ? 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode"' : 'style="width: '.$fColSize.'%;" class="dashboard"';
$sStyle = $bEditMode ? 'border: 1px #ccc dashed; width:'.$fColSize.'%;' : 'width: '.$fColSize.'%;';
$sClass = $bEditMode ? 'layout_cell edit_mode' : 'dashboard';
$iNbRows = ceil(count($aCells) / $this->iNbCols);
for($iRows = 0; $iRows < $iNbRows; $iRows++)
{
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle>");
$sCellClass = ($iRows == $iNbRows-1) ? $sClass.' layout_last_used_rank' : $sClass;
$oPage->add("<td style=\"$sStyle\" class=\"$sCellClass\" data-dashboard-cell-index=\"$iCellIdx\">");
if (array_key_exists($iCellIdx, $aCells))
{
$aDashlets = $aCells[$iCellIdx];
@@ -144,7 +146,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
}
if ($bEditMode) // Add one row for extensibility
{
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension"';
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension" data-dashboard-cell-index="'.$iCellIdx.'"';
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design>
<portals>
<portal id="legacy_portal" _delta="define">
<url>portal/index.php</url>
<rank>1.0</rank>
<handler/>
<allow>
</allow>
<deny/>
</portal>
<portal id="backoffice" _delta="define">
<url>pages/UI.php</url>
<rank>2.0</rank>
<handler/>
<allow/>
<deny>
<profile id="Portal user"/>
</deny>
</portal>
</portals>
</itop_design>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* Data Table to display a set of objects in a tabular manner in HTML
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -75,7 +75,35 @@ class DataTable
$this->oSet->SetLimit($oCustomSettings->iDefaultPageSize);
}
$this->oSet->SetOrderBy($oCustomSettings->GetSortOrder());
// Load only the requested columns
$aColumnsToLoad = array();
foreach($oCustomSettings->aColumns as $sAlias => $aColumnsInfo)
{
foreach($aColumnsInfo as $sAttCode => $aData)
{
if ($sAttCode != '_key_')
{
if ($aData['checked'])
{
$aColumnsToLoad[$sAlias][] = $sAttCode;
}
else
{
// See if this column is a must to load
$sClass = $this->aClassAliases[$sAlias];
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->alwaysLoadInTables())
{
$aColumnsToLoad[$sAlias][] = $sAttCode;
}
}
}
}
}
$this->oSet->OptimizeColumnLoad($aColumnsToLoad);
$bToolkitMenu = true;
if (isset($aExtraParams['toolkit_menu']))
{
@@ -262,17 +290,24 @@ EOF;
protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
{
$sMenuTitle = Dict::S('UI:ConfigureThisList');
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
$aActions = array(
$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
);
$this->oSet->Rewind();
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions);
$this->oSet->Rewind();
$sHtml .= $oPage->RenderPopupMenuItems($aActions);
if (!$oPage->IsPrintableVersion())
{
$sMenuTitle = Dict::S('UI:ConfigureThisList');
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png?itopversion='.ITOP_VERSION.'"><ul>';
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
$aActions = array(
$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
);
$this->oSet->Rewind();
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
$this->oSet->Rewind();
$sHtml .= $oPage->RenderPopupMenuItems($aActions);
}
else
{
$sHtml = '';
}
return $sHtml;
}
@@ -523,6 +558,7 @@ EOF;
$oPage->add_ready_script(
<<<EOF
var oTable = $('#{$this->iListId} table.listResults');
oTable.tableHover();
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
EOF
);
@@ -577,6 +613,34 @@ EOF
}
}
/**
* Simplified version of the data table with less "decoration" (and no paging)
* which is optimized for printing
*/
class PrintableDataTable extends DataTable
{
public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
return $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, -1, $bViewLink, $aExtraParams);
}
public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
if ($iPageSize < 1)
{
$iPageSize = -1; // convention: no pagination
}
$aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sHtml = $oPage->GetTable($aAttribs, $aValues);
return $sHtml;
}
}
class DataTableSettings implements Serializable
{
public $aClassAliases;
@@ -684,6 +748,12 @@ class DataTableSettings implements Serializable
{
$sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc';
}
$sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname');
if(array_key_exists($sNormalizedFName, $aSortOrder))
{
$sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc';
}
$aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort);
}
foreach($aList as $sAttCode)
@@ -736,7 +806,7 @@ class DataTableSettings implements Serializable
}
}
static public function GetTableSettings($aClassAliases, $sTableId = null)
static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false)
{
$pref = null;
$oSettings = new DataTableSettings($aClassAliases, $sTableId);
@@ -749,8 +819,11 @@ class DataTableSettings implements Serializable
if ($pref == null)
{
// Try the global preferred values for this class / set of classes
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null);
if (!$bOnlyOnTable)
{
// Try the global preferred values for this class / set of classes
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null);
}
if ($pref == null)
{
// no such settings, use the default values provided by the data model
@@ -780,12 +853,13 @@ class DataTableSettings implements Serializable
return $aSortOrder;
}
public function Save()
public function Save($sTargetTableId = null)
{
if ($this->sTableId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead
$sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId;
if ($sSaveId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead
$sSettings = $this->serialize();
appUserPreferences::SetPref($this->GetPrefsKey($this->sTableId), $sSettings);
appUserPreferences::SetPref($this->GetPrefsKey($sSaveId), $sSettings);
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,536 @@
<?php
require_once('xlsxwriter.class.php');
class ExcelExporter
{
protected $sToken;
protected $aStatistics;
protected $sState;
protected $fStartTime;
protected $oSearch;
protected $aObjectsIDs;
protected $aTableHeaders;
protected $aAuthorizedClasses;
protected $iChunkSize = 1000;
protected $iPosition;
protected $sOutputFilePath;
protected $bAdvancedMode;
public function __construct($sToken = null)
{
$this->aStatistics = array(
'objects_count' => 0,
'total_duration' => 0,
'data_retrieval_duration' => 0,
'excel_build_duration' => 0,
'excel_write_duration' => 0,
'peak_memory_usage' => 0,
);
$this->fStartTime = microtime(true);
$this->oSearch = null;
$this->sState = 'new';
$this->aObjectsIDs = array();
$this->iPosition = 0;
$this->aAuthorizedClasses = null;
$this->aTableHeaders = null;
$this->sOutputFilePath = null;
$this->bAdvancedMode = false;
$this->CheckDataDir();
if ($sToken == null)
{
$this->sToken = $this->GetNewToken();
}
else
{
$this->sToken = $sToken;
$this->ReloadState();
}
}
public function __destruct()
{
if (($this->sState != 'done') && ($this->sState != 'error') && ($this->sToken != null))
{
// Operation in progress, save the state
$this->SaveState();
}
else
{
// Operation completed, cleanup the temp files
@unlink($this->GetStateFile());
@unlink($this->GetDataFile());
}
self::CleanupOldFiles();
}
public function SetChunkSize($iChunkSize)
{
$this->iChunkSize = $iChunkSize;
}
public function SetOutputFilePath($sDestFilePath)
{
$this->sOutputFilePath = $sDestFilePath;
}
public function SetAdvancedMode($bAdvanced)
{
$this->bAdvancedMode = $bAdvanced;
}
public function SaveState()
{
$aState = array(
'state' => $this->sState,
'statistics' => $this->aStatistics,
'filter' => $this->oSearch->serialize(),
'position' => $this->iPosition,
'chunk_size' => $this->iChunkSize,
'object_ids' => $this->aObjectsIDs,
'output_file_path' => $this->sOutputFilePath,
'advanced_mode' => $this->bAdvancedMode,
);
file_put_contents($this->GetStateFile(), json_encode($aState));
return $this->sToken;
}
public function ReloadState()
{
if ($this->sToken == null)
{
throw new Exception('ExcelExporter not initialized with a token, cannot reload state');
}
if (!file_exists($this->GetStateFile()))
{
throw new Exception("ExcelExporter: missing status file '".$this->GetStateFile()."', cannot reload state.");
}
$sJson = file_get_contents($this->GetStateFile());
$aState = json_decode($sJson, true);
if ($aState === null)
{
throw new Exception("ExcelExporter:corrupted status file '".$this->GetStateFile()."', not a JSON, cannot reload state.");
}
$this->sState = $aState['state'];
$this->aStatistics = $aState['statistics'];
$this->oSearch = DBObjectSearch::unserialize($aState['filter']);
$this->iPosition = $aState['position'];
$this->iChunkSize = $aState['chunk_size'];
$this->aObjectsIDs = $aState['object_ids'];
$this->sOutputFilePath = $aState['output_file_path'];
$this->bAdvancedMode = $aState['advanced_mode'];
}
public function SetObjectList($oSearch)
{
$this->oSearch = $oSearch;
}
public function Run()
{
$sCode = 'error';
$iPercentage = 100;
$sMessage = Dict::Format('ExcelExporter:ErrorUnexpected_State', $this->sState);
$fTime = microtime(true);
try
{
switch($this->sState)
{
case 'new':
$oIDSet = new DBObjectSet($this->oSearch);
$oIDSet->OptimizeColumnLoad(array('id'));
$this->aObjectsIDs = array();
while($oObj = $oIDSet->Fetch())
{
$this->aObjectsIDs[] = $oObj->GetKey();
}
$sCode = 'retrieving-data';
$iPercentage = 5;
$sMessage = Dict::S('ExcelExporter:RetrievingData');
$this->iPosition = 0;
$this->aStatistics['objects_count'] = count($this->aObjectsIDs);
$this->aStatistics['data_retrieval_duration'] += microtime(true) - $fTime;
// The first line of the file is the "headers" specifying the label and the type of each column
$this->GetFieldsList($oIDSet, $this->bAdvancedMode);
$sRow = json_encode($this->aTableHeaders);
$hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
}
fwrite($hFile, $sRow."\n");
fclose($hFile);
// Next state
$this->sState = 'retrieving-data';
break;
case 'retrieving-data':
$oCurrentSearch = clone $this->oSearch;
$aIDs = array_slice($this->aObjectsIDs, $this->iPosition, $this->iChunkSize);
$oCurrentSearch->AddCondition('id', $aIDs, 'IN');
$hFile = @fopen($this->GetDataFile(), 'ab');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for writing.');
}
$oSet = new DBObjectSet($oCurrentSearch);
$this->GetFieldsList($oSet, $this->bAdvancedMode);
while($aObjects = $oSet->FetchAssoc())
{
$aRow = array();
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias];
if ($this->bAdvancedMode)
{
$aRow[] = $oObj->GetKey();
}
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$value = $oObj->Get($sAttCodeEx);
if ($value instanceOf ormCaseLog)
{
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
$sExcelVal = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
}
else
{
$sExcelVal = $oAttDef->GetEditValue($value, $oObj);
}
$aRow[] = $sExcelVal;
}
}
$sRow = json_encode($aRow);
fwrite($hFile, $sRow."\n");
}
fclose($hFile);
if (($this->iPosition + $this->iChunkSize) > count($this->aObjectsIDs))
{
// Next state
$this->sState = 'building-excel';
$sCode = 'building-excel';
$iPercentage = 80;
$sMessage = Dict::S('ExcelExporter:BuildingExcelFile');
}
else
{
$sCode = 'retrieving-data';
$this->iPosition += $this->iChunkSize;
$iPercentage = 5 + round(75 * ($this->iPosition / count($this->aObjectsIDs)));
$sMessage = Dict::S('ExcelExporter:RetrievingData');
}
break;
case 'building-excel':
$hFile = @fopen($this->GetDataFile(), 'rb');
if ($hFile === false)
{
throw new Exception('ExcelExporter: Failed to open temporary data file: "'.$this->GetDataFile().'" for reading.');
}
$sHeaders = fgets($hFile);
$aHeaders = json_decode($sHeaders, true);
$aData = array();
while($sLine = fgets($hFile))
{
$aRow = json_decode($sLine);
$aData[] = $aRow;
}
fclose($hFile);
@unlink($this->GetDataFile());
$fStartExcel = microtime(true);
$writer = new XLSXWriter();
$writer->setAuthor(UserRights::GetUserFriendlyName());
$writer->writeSheet($aData,'Sheet1', $aHeaders);
$fExcelTime = microtime(true) - $fStartExcel;
$this->aStatistics['excel_build_duration'] = $fExcelTime;
$fTime = microtime(true);
$writer->writeToFile($this->GetExcelFilePath());
$fExcelSaveTime = microtime(true) - $fTime;
$this->aStatistics['excel_write_duration'] = $fExcelSaveTime;
// Next state
$this->sState = 'done';
$sCode = 'done';
$iPercentage = 100;
$sMessage = Dict::S('ExcelExporter:Done');
break;
case 'done':
$this->sState = 'done';
$sCode = 'done';
$iPercentage = 100;
$sMessage = Dict::S('ExcelExporter:Done');
break;
}
}
catch(Exception $e)
{
$sCode = 'error';
$sMessage = $e->getMessage();
}
$this->aStatistics['total_duration'] += microtime(true) - $fTime;
$peak_memory = memory_get_peak_usage(true);
if ($peak_memory > $this->aStatistics['peak_memory_usage'])
{
$this->aStatistics['peak_memory_usage'] = $peak_memory;
}
return array(
'code' => $sCode,
'message' => $sMessage,
'percentage' => $iPercentage,
);
}
public function GetExcelFilePath()
{
if ($this->sOutputFilePath == null)
{
return APPROOT.'data/bulk_export/'.$this->sToken.'.xlsx';
}
else
{
return $this->sOutputFilePath;
}
}
public static function GetExcelFileFromToken($sToken)
{
return @file_get_contents(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
}
public static function CleanupFromToken($sToken)
{
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.status');
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.data');
@unlink(APPROOT.'data/bulk_export/'.$sToken.'.xlsx');
}
public function Cleanup()
{
self::CleanupFromToken($this->sToken);
}
/**
* Delete all files in the data/bulk_export directory which are older than 1 day
* unless a different delay is configured.
*/
public static function CleanupOldFiles()
{
$aFiles = glob(APPROOT.'data/bulk_export/*.*');
$iDelay = MetaModel::GetConfig()->Get('xlsx_exporter_cleanup_old_files_delay');
if($iDelay > 0)
{
foreach($aFiles as $sFile)
{
$iModificationTime = filemtime($sFile);
if($iModificationTime < (time() - $iDelay))
{
// Temporary files older than one day are deleted
//echo "Supposed to delete: '".$sFile." (Unix Modification Time: $iModificationTime)'\n";
@unlink($sFile);
}
}
}
}
public function DisplayStatistics(Page $oPage)
{
$aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
);
if ($oPage instanceof CLIPage)
{
$oPage->add($this->GetStatistics('text'));
}
else
{
$oPage->add($this->GetStatistics('html'));
}
}
public function GetStatistics($sFormat = 'html')
{
$sStats = '';
$aStats = array(
'Number of objects exported' => $this->aStatistics['objects_count'],
'Total export duration' => sprintf('%.3f s', $this->aStatistics['total_duration']),
'Data retrieval duration' => sprintf('%.3f s', $this->aStatistics['data_retrieval_duration']),
'Excel build duration' => sprintf('%.3f s', $this->aStatistics['excel_build_duration']),
'Excel write duration' => sprintf('%.3f s', $this->aStatistics['excel_write_duration']),
'Peak memory usage' => self::HumanDisplay($this->aStatistics['peak_memory_usage']),
);
if ($sFormat == 'text')
{
foreach($aStats as $sLabel => $sValue)
{
$sStats .= "+------------------------------+----------+\n";
$sStats .= sprintf("|%-30s|%10s|\n", $sLabel, $sValue);
}
$sStats .= "+------------------------------+----------+";
}
else
{
$sStats .= '<table><tbody>';
foreach($aStats as $sLabel => $sValue)
{
$sStats .= "<tr><td>$sLabel</td><td>$sValue</td></tr>";
}
$sStats .= '</tbody></table>';
}
return $sStats;
}
public static function HumanDisplay($iSize)
{
$aUnits = array('B','KB','MB','GB','TB','PB');
return @round($iSize/pow(1024,($i=floor(log($iSize,1024)))),2).' '.$aUnits[$i];
}
protected function CheckDataDir()
{
if(!is_dir(APPROOT."data/bulk_export"))
{
@mkdir(APPROOT."data/bulk_export", 0777, true /* recursive */);
clearstatcache();
}
if (!is_writable(APPROOT."data/bulk_export"))
{
throw new Exception('Data directory "'.APPROOT.'data/bulk_export" could not be written.');
}
}
protected function GetStateFile($sToken = null)
{
if ($sToken == null)
{
$sToken = $this->sToken;
}
return APPROOT."data/bulk_export/$sToken.status";
}
protected function GetDataFile()
{
return APPROOT.'data/bulk_export/'.$this->sToken.'.data';
}
protected function GetNewToken()
{
$iNum = rand();
do
{
$iNum++;
$sToken = sprintf("%08x", $iNum);
$sFileName = $this->GetStateFile($sToken);
$hFile = @fopen($sFileName, 'x');
}
while($hFile === false);
fclose($hFile);
return $sToken;
}
protected function GetFieldsList($oSet, $bFieldsAdvanced = false, $bLocalize = true, $aFields = null)
{
$this->aFieldsList = array();
$oAppContext = new ApplicationContext();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$this->aAuthorizedClasses = array();
foreach($aClasses as $sAlias => $sClassName)
{
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
{
$this->aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aAttribs = array();
$this->aTableHeaders = array();
foreach($this->aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
if ($bFieldsAdvanced)
{
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE))
{
$sRemoteClass = $oAttDef->GetTargetClass();
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
{
$this->aFieldsList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode);
}
}
}
}
else
{
// Any other attribute
$this->aFieldsList[$sAlias][$sAttCodeEx] = $oAttDef;
}
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$this->aFieldsList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
if ($bFieldsAdvanced)
{
$this->aTableHeaders['id'] = '0';
}
foreach($this->aFieldsList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$sLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx;
if($oAttDef instanceof AttributeDateTime)
{
$this->aTableHeaders[$sLabel] = 'datetime';
}
else
{
$this->aTableHeaders[$sLabel] = 'string';
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,83 +20,86 @@
/**
* Class LoginWebPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT."/application/nicewebpage.class.inc.php");
require_once(APPROOT.'/application/portaldispatcher.class.inc.php');
/**
* Web page used for displaying the login form
*/
class LoginWebPage extends NiceWebPage
{
const EXIT_PROMPT = 0;
const EXIT_HTTP_401 = 1;
const EXIT_RETURN = 2;
const EXIT_CODE_OK = 0;
const EXIT_CODE_MISSINGLOGIN = 1;
const EXIT_CODE_MISSINGPASSWORD = 2;
const EXIT_CODE_WRONGCREDENTIALS = 3;
const EXIT_CODE_MUSTBEADMIN = 4;
const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5;
protected static $sHandlerClass = __class__;
public static function RegisterHandler($sClass)
{
self::$sHandlerClass = $sClass;
}
public static function NewLoginWebPage()
{
return new self::$sHandlerClass;
}
protected static $m_sLoginFailedMessage = '';
public function __construct()
{
parent::__construct("iTop Login");
$this->add_style(<<<EOF
body {
background: #eee;
margin: 0;
padding: 0;
}
#login-logo {
margin-top: 150px;
width: 300px;
padding-left: 20px;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
margin-left: auto;
margin-right: auto;
background: #f6f6f1;
height: 54px;
border-top: 1px solid #000;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-bottom: 0;
text-align: center;
}
#login-logo img {
border: 0;
}
#login {
width: 300px;
margin-left: auto;
margin-right: auto;
padding: 20px;
background-color: #fff;
border-bottom: 1px solid #000;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-top: 0;
text-align: center;
}
#pwd, #user,#old_pwd, #new_pwd, #retype_new_pwd {
width: 10em;
}
.center {
text-align: center;
}
h1 {
color: #1C94C4;
font-size: 16pt;
}
.v-spacer {
padding-top: 1em;
}
EOF
);
public function __construct($sTitle = 'iTop Login')
{
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->add_header("Cache-control: no-cache");
}
public function SetStyleSheet()
{
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
}
public static function SetLoginFailedMessage($sMessage)
{
self::$m_sLoginFailedMessage = $sMessage;
}
public function EnableResetPassword()
{
return MetaModel::GetConfig()->Get('forgot_password');
}
public function DisplayLoginHeader($bMainAppLogo = false)
{
if ($bMainAppLogo)
{
$sLogo = 'itop-logo.png';
$sBrandingLogo = 'main-logo.png';
}
else
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
}
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?itopversion='.ITOP_VERSION;
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?itopversion='.ITOP_VERSION;
}
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
}
public function DisplayLoginForm($sLoginType, $bFailedLogin = false)
{
switch($sLoginType)
@@ -111,6 +114,7 @@ EOF
case 'url':
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
$this->add_header('HTTP/1.0 401 Unauthorized');
$this->add_header('Content-type: text/html; charset=iso-8859-1');
// Note: displayed when the user will click on Cancel
$this->add('<p><strong>'.Dict::S('UI:Login:Error:AccessRestricted').'</strong></p>');
break;
@@ -121,9 +125,7 @@ EOF
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"../images/itop-logo-external.png\"></a></div>\n");
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
if ($bFailedLogin)
@@ -142,12 +144,39 @@ EOF
$this->add("<p>".Dict::S('UI:Login:IdentifyYourself')."</p>\n");
}
$this->add("<form method=\"post\">\n");
$this->add("<table width=\"100%\">\n");
$this->add("<table>\n");
$sForgotPwd = $this->EnableResetPassword() ? $this->ForgotPwdLink() : '';
$this->add("<tr><td style=\"text-align:right\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label></td><td style=\"text-align:left\"><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"pwd\">".Dict::S('UI:Login:PasswordPrompt').":</label></td><td style=\"text-align:left\"><input id=\"pwd\" type=\"password\" name=\"auth_pwd\" value=\"".htmlentities($sAuthPwd, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"> <input type=\"submit\" value=\"".Dict::S('UI:Button:Login')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Login')."\" /></span></td></tr>\n");
if (strlen($sForgotPwd) > 0)
{
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\">$sForgotPwd</td></tr>\n");
}
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"login\" />\n");
$this->add_ready_script('$("#user").focus();');
// Keep the OTHER parameters posted
foreach($_POST as $sPostedKey => $postedValue)
{
if (!in_array($sPostedKey, array('auth_user', 'auth_pwd')))
{
if (is_array($postedValue))
{
foreach($postedValue as $sKey => $sValue)
{
$this->add("<input type=\"hidden\" name=\"".htmlentities($sPostedKey, ENT_QUOTES, 'UTF-8')."[".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n");
}
}
else
{
$this->add("<input type=\"hidden\" name=\"".htmlentities($sPostedKey, ENT_QUOTES, 'UTF-8')."\" value=\"".htmlentities($postedValue, ENT_QUOTES, 'UTF-8')."\" />\n");
}
}
}
$this->add("</form>\n");
$this->add(Dict::S('UI:Login:About'));
$this->add("</div>\n");
@@ -155,11 +184,210 @@ EOF
}
}
/**
* Return '' to disable this feature
*/
public function ForgotPwdLink()
{
$sUrl = '?loginop=forgot_pwd';
$sHtml = "<a href=\"$sUrl\" target=\"_blank\">".Dict::S('UI:Login:ForgotPwd')."</a>";
return $sHtml;
}
public function DisplayForgotPwdForm($bFailedToReset = false, $sFailureReason = null)
{
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ForgotPwdForm')."</h1>\n");
$this->add("<p>".Dict::S('UI:Login:ForgotPwdForm+')."</p>\n");
if ($bFailedToReset)
{
$this->add("<p class=\"hilite\">".Dict::Format('UI:Login:ResetPwdFailed', htmlentities($sFailureReason, ENT_QUOTES, 'UTF-8'))."</p>\n");
}
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td colspan=\"2\" class=\"center\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"button\" onClick=\"window.close();\" value=\"".Dict::S('UI:Button:Cancel')."\" /></span>&nbsp;&nbsp;<span class=\"btn_border\"><input type=\"submit\" value=\"".Dict::S('UI:Login:ResetPassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"forgot_pwd_go\" />\n");
$this->add("</form>\n");
$this->add("</div>\n");
$this->add_ready_script('$("#user").focus();');
}
protected function ForgotPwdGo()
{
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
try
{
UserRights::Login($sAuthUser); // Set the user's language (if possible!)
$oUser = UserRights::GetUserObject();
if ($oUser == null)
{
throw new Exception(Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser));
}
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token'))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
}
if (!$oUser->CanChangePassword())
{
throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd'));
}
$sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed
if ($sTo == '')
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail'));
}
// This token allows the user to change the password without knowing the previous one
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
$oUser->Set('reset_pwd_token', $sToken);
CMDBObject::SetTrackInfo('Reset password');
$oUser->DBUpdate();
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
if ($sFrom == '')
{
$sFrom = $sTo;
}
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject'));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
$oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl));
$iRes = $oEmail->Send($aIssues, true /* force synchronous exec */);
switch ($iRes)
{
//case EMAIL_SEND_PENDING:
case EMAIL_SEND_OK:
break;
case EMAIL_SEND_ERROR:
default:
IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
}
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ForgotPwdForm')."</h1>\n");
$this->add("<p>".Dict::S('UI:ResetPwd-EmailSent')."</p>");
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><input type=\"button\" onClick=\"window.close();\" value=\"".Dict::S('UI:Button:Done')."\" /></td></tr>\n");
$this->add("</table>\n");
$this->add("</form>\n");
$this->add("</div\n");
}
catch(Exception $e)
{
$this->DisplayForgotPwdForm(true, $e->getMessage());
}
}
public function DisplayResetPwdForm()
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sToken = utils::ReadParam('token', '', false, 'raw_data');
UserRights::Login($sAuthUser); // Set the user's language
$oUser = UserRights::GetUserObject();
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:ResetPwd-Title')."</h1>\n");
if ($oUser == null)
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
else
{
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
<<<EOF
function DoCheckPwd()
{
if ($('#new_pwd').val() != $('#retype_new_pwd').val())
{
alert('$sInconsistenPwdMsg');
return false;
}
return true;
}
EOF
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
}
}
}
public function DoResetPassword()
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sToken = utils::ReadParam('token', '', false, 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
UserRights::Login($sAuthUser); // Set the user's language
$oUser = UserRights::GetUserObject();
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:ResetPwd-Title')."</h1>\n");
if ($oUser == null)
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
else
{
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
if (!$oEncryptedPassword->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
}
$this->add("</div\n");
}
}
public function DisplayChangePwdForm($bFailedLogin = false)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(<<<EOF
function GoBack()
@@ -178,8 +406,7 @@ function DoCheckPwd()
}
EOF
);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"../images/itop-logo.png\"></a></div>\n");
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ChangeYourPassword')."</h1>\n");
if ($bFailedLogin)
@@ -187,11 +414,11 @@ EOF
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectOldPassword')."</p>\n");
}
$this->add("<form method=\"post\">\n");
$this->add("<table width=\"100%\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"old_pwd\">".Dict::S('UI:Login:OldPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"old_pwd\" name=\"old_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"> <input type=\"button\" onClick=\"GoBack();\" value=\"".Dict::S('UI:Button:Cancel')."\" />&nbsp;&nbsp;<input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"button\" onClick=\"GoBack();\" value=\"".Dict::S('UI:Button:Cancel')."\" /></span>&nbsp;&nbsp;<span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_change_pwd\" />\n");
$this->add("</form>\n");
@@ -200,17 +427,10 @@ EOF
static function ResetSession()
{
if (isset($_SESSION['login_mode']))
{
$sPreviousLoginMode = $_SESSION['login_mode'];
}
else
{
$sPreviousLoginMode = '';
}
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_mode']);
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
}
@@ -220,20 +440,33 @@ EOF
return MetaModel::GetConfig()->GetSecureConnectionRequired();
}
static function IsConnectionSecure()
/**
* Guess if a string looks like an UTF-8 string based on some ranges of multi-bytes encoding
* @param string $sString
* @return bool True if the string contains some typical UTF-8 multi-byte sequences
*/
static function LooksLikeUTF8($sString)
{
$bSecured = false;
if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off'))
{
$bSecured = true;
}
return $bSecured;
return preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
|[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
|\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)+%xs', $sString);
}
protected static function Login()
/**
* Attempt a login
*
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
* @return int One of the class constants EXIT_CODE_...
*/
protected static function Login($iOnExit)
{
if (self::SecureConnectionRequired() && !self::IsConnectionSecure())
if (self::SecureConnectionRequired() && !utils::IsConnectionSecure())
{
// Non secured URL... request for a secure connection
throw new Exception('Secure connection required!');
@@ -245,128 +478,221 @@ EOF
{
//echo "User: ".$_SESSION['auth_user']."\n";
// Already authentified
UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
return true;
}
else
{
$index = 0;
$sLoginMode = '';
$sAuthentication = 'internal';
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
if ($bRet)
{
$sLoginType = $aAllowedLoginTypes[$index];
switch($sLoginType)
{
case 'cas':
utils::InitCASClient();
// check CAS authentication
if (phpCAS::isAuthenticated())
{
$sAuthUser = phpCAS::getUser();
$sAuthPwd = '';
$sLoginMode = 'cas';
$sAuthentication = 'external';
}
break;
case 'form':
// iTop standard mode: form based authentication
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', '', false, 'raw_data');
if ($sAuthUser != '')
{
$sLoginMode = 'form';
}
break;
case 'basic':
// Standard PHP authentication method, works with Apache...
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
$sLoginMode = 'basic';
}
else if (isset($_SERVER['PHP_AUTH_USER']))
{
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
$sLoginMode = 'basic';
}
break;
case 'external':
// Web server supplied authentication
$bExternalAuth = false;
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
if ($sAuthUser && (strlen($sAuthUser) > 0))
{
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
$sLoginMode = 'external';
$sAuthentication = 'external';
}
break;
case 'url':
// Credentials passed directly in the url
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (($sAuthUser != '') && ($sAuthPwd != null))
{
$sLoginMode = 'url';
}
break;
}
$index++;
return self::EXIT_CODE_OK;
}
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
if ($sLoginMode == '')
// The user account is no longer valid/enabled
static::ResetSession();
}
$index = 0;
$sLoginMode = '';
$sAuthentication = 'internal';
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
{
$sLoginType = $aAllowedLoginTypes[$index];
switch($sLoginType)
{
// First connection
$sDesiredLoginMode = utils::ReadParam('login_mode');
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
case 'cas':
utils::InitCASClient();
// check CAS authentication
if (phpCAS::isAuthenticated())
{
$sLoginMode = $sDesiredLoginMode;
$sAuthUser = phpCAS::getUser();
$sAuthPwd = '';
$sLoginMode = 'cas';
$sAuthentication = 'external';
}
break;
case 'form':
// iTop standard mode: form based authentication
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data');
if (($sAuthUser != '') && ($sAuthPwd !== null))
{
$sLoginMode = 'form';
}
break;
case 'basic':
// Standard PHP authentication method, works with Apache...
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
$sLoginMode = 'basic';
}
else if (isset($_SERVER['PHP_AUTH_USER']))
{
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
// Unfortunately, the RFC is not clear about the encoding...
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
if (!self::LooksLikeUTF8($sAuthUser))
{
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
// Supposed to be harmless in case of a plain ASCII string...
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
}
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
if (!self::LooksLikeUTF8($sAuthPwd))
{
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
// Supposed to be harmless in case of a plain ASCII string...
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
}
$sLoginMode = 'basic';
}
break;
case 'external':
// Web server supplied authentication
$bExternalAuth = false;
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
if ($sAuthUser && (strlen($sAuthUser) > 0))
{
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
$sLoginMode = 'external';
$sAuthentication = 'external';
}
break;
case 'url':
// Credentials passed directly in the url
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (($sAuthUser != '') && ($sAuthPwd !== null))
{
$sLoginMode = 'url';
}
break;
}
$index++;
}
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
if ($sLoginMode == '')
{
// 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))
{
return self::EXIT_CODE_MISSINGPASSWORD;
}
else
{
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
return self::EXIT_CODE_MISSINGLOGIN;
}
$oPage = new LoginWebPage();
}
else
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
$oPage->output();
exit;
}
else
}
else
{
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
{
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'))
{
//echo "Check Credentials returned false for user $sAuthUser!";
self::ResetSession();
$oPage = new LoginWebPage();
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
}
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'))
{
// 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;
$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;
}
/**
* Overridable: depending on the user, head toward a dedicated portal
* @param string|null $sRequestedPortalId
* @param int $iOnExit How to complete the call: redirect or return a code
*/
protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT)
{
$fStart = microtime(true);
$ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId);
if ($ret === true)
{
return self::EXIT_CODE_OK;
}
else if($ret === false)
{
throw new Exception('Nowhere to go??');
}
else
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED;
}
else
{
// No rights to be here, redirect to the portal
header('Location: '.$ret);
}
}
}
@@ -377,12 +703,60 @@ EOF
* - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected to the portal
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
* @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
*/
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false)
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
{
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
}
/**
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
* @param string|null $sRequestedPortalId The requested "portal" interface, null for any
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
*/
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
{
$sMessage = ''; // In case we need to return a message to the calling web page
$operation = utils::ReadParam('loginop', '');
$sMessage = self::HandleOperations($operation); // May exit directly
$iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK)
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_MUSTBEADMIN;
}
else
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
exit;
}
}
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
}
if ($iOnExit == self::EXIT_RETURN)
{
return $iRet;
}
else
{
return $sMessage;
}
}
protected static function HandleOperations($operation)
{
$sMessage = ''; // most of the operations never return, but some can return a message to be displayed
if ($operation == 'logoff')
{
if (isset($_SESSION['login_mode']))
@@ -402,16 +776,44 @@ EOF
}
}
self::ResetSession();
$oPage = new LoginWebPage();
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */);
$oPage->output();
exit;
}
}
else if ($operation == 'forgot_pwd')
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayForgotPwdForm();
$oPage->output();
exit;
}
else if ($operation == 'forgot_pwd_go')
{
$oPage = self::NewLoginWebPage();
$oPage->ForgotPwdGo();
$oPage->output();
exit;
}
else if ($operation == 'reset_pwd')
{
$oPage = self::NewLoginWebPage();
$oPage->DisplayResetPwdForm();
$oPage->output();
exit;
}
else if ($operation == 'do_reset_pwd')
{
$oPage = self::NewLoginWebPage();
$oPage->DoResetPassword();
$oPage->output();
exit;
}
else if ($operation == 'change_pwd')
{
$sAuthUser = $_SESSION['auth_user'];
UserRights::Login($sAuthUser); // Set the user's language
$oPage = new LoginWebPage();
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm();
$oPage->output();
exit;
@@ -424,30 +826,36 @@ EOF
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
{
$oPage = new LoginWebPage();
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm(true); // old pwd was wrong
$oPage->output();
exit;
}
$sMessage = Dict::S('UI:Login:PasswordChanged');
}
self::Login();
if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
exit;
}
elseif ( (!$bIsAllowedToPortalUsers) && (UserRights::IsPortalUser()))
{
// No rights to be here, redirect to the portal
header('Location: '.utils::GetAbsoluteUrlAppRoot().'portal/index.php');
}
return $sMessage;
}
}
protected static function Dispatch($sRequestedPortalId)
{
if ($sRequestedPortalId === null) return true; // allowed to any portal => return true
$aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = array();
foreach($aPortalsConf as $sPortalId => $aConf)
{
$sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
}
if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed())
{
return true;
}
foreach($aDispatchers as $sPortalId => $oDispatcher)
{
if ($oDispatcher->IsUserAllowed()) return $oDispatcher->GetUrl();
}
return false; // nothing matched !!
}
} // End of class
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Construction and display of the application's main menu
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -184,8 +184,12 @@ class ApplicationMenu
$oPage->AddToMenu('</ul>');
if ($bActive)
{
$oPage->add_ready_script("$('#accordion').accordion('activate', $iAccordion);");
$oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true});"); // Make it auto-collapsible once it has been opened properly
$oPage->add_ready_script(
<<<EOF
// Accordion Menu
$("#accordion").css({display:'block'}).accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: true, active: $iAccordion, icons: false, animate:true }); // collapsible will be enabled once the item will be selected
EOF
);
}
}
$oPage->AddToMenu('</div>');
@@ -296,14 +300,26 @@ class ApplicationMenu
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null)
{
// Make sure the root menu is sorted on 'rank'
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$oFirstGroup = self::GetMenuNode(self::$aRootMenus[0]['index']);
$oMenuNode = self::GetMenuNode(self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'][0]['index']);
$sMenuId = $oMenuNode->GetMenuId();
$sMenuId = self::GetDefaultMenuId();
}
return $sMenuId;
}
static public function GetDefaultMenuId()
{
static $sDefaultMenuId = null;
if (is_null($sDefaultMenuId))
{
// Make sure the root menu is sorted on 'rank'
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$oFirstGroup = self::GetMenuNode(self::$aRootMenus[0]['index']);
$aChildren = self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'];
usort($aChildren, array('ApplicationMenu', 'CompareOnRank'));
$oMenuNode = self::GetMenuNode($aChildren[0]['index']);
$sDefaultMenuId = $oMenuNode->GetMenuId();
}
return $sDefaultMenuId;
}
}
/**
@@ -336,6 +352,7 @@ abstract class MenuNode
{
protected $sMenuId;
protected $index;
protected $iParentIndex;
/**
* Properties reflecting how the node has been declared
@@ -376,6 +393,7 @@ abstract class MenuNode
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
{
$this->sMenuId = $sMenuId;
$this->iParentIndex = $iParentIndex;
$this->aReflectionProperties = array();
if (strlen($sEnableClass) > 0)
{
@@ -408,7 +426,21 @@ abstract class MenuNode
public function GetLabel()
{
return Dict::S("Menu:$this->sMenuId+", "");
$sRet = Dict::S("Menu:$this->sMenuId+", "");
if ($sRet === '')
{
if ($this->iParentIndex != -1)
{
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
$sRet = $oParentMenu->GetTitle().' / '.$this->GetTitle();
}
else
{
$sRet = $this->GetTitle();
}
//$sRet = $this->GetTitle();
}
return $sRet;
}
public function GetIndex()
@@ -589,6 +621,7 @@ class OQLMenuNode extends MenuNode
protected $sPageTitle;
protected $sOQL;
protected $bSearch;
protected $bSearchFormOpen;
/**
* Extra parameters to be passed to the display block to fine tune its appearence
@@ -608,12 +641,20 @@ class OQLMenuNode extends MenuNode
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
* @return MenuNode
*/
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null, $bSearchFormOpen = null)
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sPageTitle = "Menu:$sMenuId+";
$this->sOQL = $sOQL;
$this->bSearch = $bSearch;
if ($bSearchFormOpen == null)
{
$this->bSearchFormOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
}
else
{
$this->bSearchFormOpen = $bSearchFormOpen;
}
$this->m_aParams = array();
$this->aReflectionProperties['oql'] = $sOQL;
$this->aReflectionProperties['do_search'] = $bSearch;
@@ -642,13 +683,14 @@ class OQLMenuNode extends MenuNode
Dict::S($this->sPageTitle),
'Menu_'.$this->GetMenuId(),
$this->bSearch, // Search pane
true, // Search open
$this->bSearchFormOpen, // Search open
$oPage,
array_merge($this->m_aParams, $aExtraParams)
array_merge($this->m_aParams, $aExtraParams),
true
);
}
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array())
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array(), $bEnableBreadcrumb = false)
{
$sUsageId = utils::GetSafeId($sUsageId);
$oSearch = DBObjectSearch::FromOQL($sOql);
@@ -666,6 +708,15 @@ class OQLMenuNode extends MenuNode
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, $sUsageId);
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage))
{
// Breadcrumb
//$iCount = $oBlock->GetDisplayedCount();
$sPageId = "ui-search-".$oSearch->GetClass();
$sLabel = MetaModel::GetName($oSearch->GetClass());
$oPage->SetBreadCrumbEntry($sPageId, $sLabel, $sTitle, '', '../images/breadcrumb-search.png');
}
}
}
@@ -696,9 +747,11 @@ class SearchMenuNode extends MenuNode
$this->sClass = $sClass;
$this->aReflectionProperties['class'] = $sClass;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/search.png');
$oSearch = new DBObjectSearch($this->sClass);
$aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
@@ -876,14 +929,70 @@ class DashboardMenuNode extends MenuNode
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
$sDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $this->sMenuId);
$oPage->add('<div class="dashboard_contents" id="'.$sDivId.'">');
$oDashboard->Render($oPage, false, $aExtraParams);
$oPage->add('</div>');
$oDashboard->RenderEditionTools($oPage);
if ($oDashboard->GetAutoReload())
{
$sId = $this->sMenuId;
$sExtraParams = json_encode($aExtraParams);
$iReloadInterval = 1000 * $oDashboard->GetAutoReloadInterval();
$oPage->add_script(
<<<EOF
setInterval("ReloadDashboard('$sDivId');", $iReloadInterval);
function ReloadDashboard(sDivId)
{
var oExtraParams = $sExtraParams;
// Do not reload when a dialog box is active
if (!($('.ui-dialog:visible').length > 0))
{
$('.dashboard_contents#'+sDivId).block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'reload_dashboard', dashboard_id: '$sId', extra_params: oExtraParams},
function(data){
$('.dashboard_contents#'+sDivId).html(data);
$('.dashboard_contents#'+sDivId).unblock();
}
);
}
}
EOF
);
}
$bEdit = utils::ReadParam('edit', false);
if ($bEdit)
{
$sId = addslashes($this->sMenuId);
$oPage->add_ready_script("EditDashboard('$sId');");
}
else
{
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
$sParentTitle = $oParentMenu->GetTitle();
$sThisTitle = $this->GetTitle();
if ($sParentTitle != $sThisTitle)
{
$sDescription = $sParentTitle.' / '.$sThisTitle;
}
else
{
$sDescription = $sThisTitle;
}
if ($this->sMenuId == ApplicationMenu::GetDefaultMenuId())
{
$sIcon = '../images/breadcrumb_home.png';
}
else
{
$sIcon = '../images/breadcrumb-dashboard.png';
}
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon);
}
}
else
{
@@ -914,7 +1023,7 @@ class DashboardMenuNode extends MenuNode
}
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class NiceWebPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -33,22 +33,23 @@ class NiceWebPage extends WebPage
var $m_aReadyScripts;
var $m_sRootUrl;
public function __construct($s_title)
public function __construct($s_title, $bPrintable = false)
{
parent::__construct($s_title);
parent::__construct($s_title, $bPrintable);
$this->m_aReadyScripts = array();
$this->add_linked_script("../js/jquery-1.7.1.min.js");
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.8.17.custom.css');
$this->add_linked_script('../js/jquery-ui-1.8.17.custom.min.js');
$this->add_linked_script("../js/hovertip.js");
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-1.10.0.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate-1.2.1.min.js'); // Needed since many other plugins still rely on oldies like $.browser
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/ui-lightness/jquery-ui-1.10.3.custom.min.css');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.10.3.custom.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
// table sorting
$this->add_linked_script("../js/jquery.tablesorter.js");
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
$this->add_linked_script("../js/jquery.tablehover.js");
$this->add_linked_script('../js/field_sorter.js');
$this->add_linked_script('../js/datatable.js');
$this->add_linked_script("../js/jquery.positionBy.js");
$this->add_linked_script("../js/jquery.popupmenu.js");
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js');
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
@@ -93,11 +94,12 @@ class NiceWebPage extends WebPage
$("table.listResults").tableHover(); // hover tables
EOF
);
$this->add_linked_stylesheet("../css/light-grey.css");
$this->add_saas("css/light-grey.scss");
$this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot();
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$sAbsURLModulesRoot = addslashes($this->GetAbsoluteUrlModulesRoot());
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$sAppContext = addslashes($this->GetApplicationContext());
@@ -112,6 +114,23 @@ function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
function GetAbsoluteUrlModulePage(sModule, sPage, aArguments)
{
// aArguments is optional, it default to an empty hash
aArguments = typeof aArguments !== 'undefined' ? aArguments : {};
var sUrl = '$sAbsURLAppRoot'+'pages/exec.php?exec_module='+sModule+'&exec_page='+sPage+'&exec_env='+'$sEnvironment';
for (var sArgName in aArguments)
{
if (aArguments.hasOwnProperty(sArgName))
{
sUrl = sUrl + '&'+sArgName+'='+aArguments[sArgname];
}
}
return sUrl;
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
@@ -200,7 +219,7 @@ EOF
*/
public function output()
{
$this->set_base($this->m_sRootUrl.'pages/');
//$this->set_base($this->m_sRootUrl.'pages/');
if (count($this->m_aReadyScripts)>0)
{
$this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});");

View File

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

View File

@@ -0,0 +1,66 @@
<?php
class PortalDispatcher
{
protected $sPortalid;
protected $aData;
public function __construct($sPortalId)
{
$this->sPortalid = $sPortalId;
$this->aData = PortalDispatcherData::GetData($sPortalId);
}
public function IsUserAllowed()
{
$bRet = true;
$aProfiles = UserRights::ListProfiles();
foreach($this->aData['deny'] as $sDeniedProfile)
{
// If one denied profile is present, it's enough => return false
if (in_array($sDeniedProfile, $aProfiles))
{
return false;
}
}
// If there are some "allow" profiles, then by default the result is false
// since the user must have at least one of the profiles to be allowed
if (count($this->aData['allow']) > 0)
{
$bRet = false;
}
foreach($this->aData['allow'] as $sAllowProfile)
{
// If one "allow" profile is present, it's enough => return true
if (in_array($sAllowProfile, $aProfiles))
{
return true;
}
}
return $bRet;
}
public function GetURL()
{
$aOverloads = MetaModel::GetConfig()->Get('portal_dispatch_urls');
if (array_key_exists($this->sPortalid, $aOverloads))
{
$sRet = $aOverloads[$this->sPortalid];
}
else
{
$sRet = utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
}
return $sRet;
}
public function GetLabel()
{
return Dict::S('portal:'.$this->sPortalid);
}
public function GetRank()
{
return $this->aData['rank'];
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Class PortalWebPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -49,9 +49,12 @@ class PortalWebPage extends NiceWebPage
*/
protected $m_sWelcomeMsg;
protected $m_aMenuButtons;
protected $m_oCtx;
public function __construct($sTitle, $sAlternateStyleSheet = '')
{
$this->m_oCtx = new ContextTag('GUI:Portal');
$this->m_sWelcomeMsg = '';
$this->m_aMenuButtons = array();
parent::__construct($sTitle);
@@ -64,6 +67,7 @@ class PortalWebPage extends NiceWebPage
$sAbsURLModulesRoot = addslashes(utils::GetAbsoluteUrlModulesRoot()); // Pass it to Javascript scripts
$oAppContext = new ApplicationContext();
$sAppContext = addslashes($oAppContext->GetForLink());
$this->add_dict_entry('UI:FillAllMandatoryFields');
if ($sAlternateStyleSheet != '')
{
$this->add_linked_stylesheet("../portal/$sAlternateStyleSheet/portal.css");
@@ -87,8 +91,21 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_script("../js/forms-json-utils.js");
$this->add_linked_script("../js/swfobject.js");
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script('../js/jquery.multiselect.min.js');
$this->add_linked_script('../js/jquery.multiselect.js');
$this->add_linked_script("../js/ajaxfileupload.js");
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$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');
$this->add_ready_script(
<<<EOF
try
@@ -130,16 +147,52 @@ try
});
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
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
});
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry_html').toggle(); });
$(document).ajaxSend(function(event, jqxhr, options) {
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
});
$(document).ajaxError(function(event, jqxhr, options) {
if (jqxhr.status == 401)
{
$('<div>'+$sJSDisconnectedMessage+'</div>').dialog({
modal:true,
title: $sJSTitle,
close: function() { $(this).remove(); },
minWidth: 400,
buttons: [
{ text: $sJSLoginAgain, click: function() { window.location.href= GetAbsoluteUrlAppRoot()+'pages/UI.php' } },
{ text: $sJSStayOnThePage, click: function() { $(this).dialog('close'); } }
]
});
}
});
}
catch(err)
{
@@ -210,7 +263,7 @@ EOF
{
var form = $('FORM');
form.unbind('submit'); // De-activate validation
window.location.href = '?operation=';
window.location.href = window.location.href.replace(/operation=[^&]*&?/, '');
return false;
}
@@ -219,11 +272,36 @@ EOF
var next_step = $('input[id=next_step]');
next_step.val(sStep);
}
// For disabling the CKEditor at init time when the corresponding textarea is disabled !
CKEDITOR.plugins.add( 'disabler',
{
init : function( editor )
{
editor.on( 'instanceReady', function(e)
{
e.removeListener();
$('#'+ editor.name).trigger('update');
});
}
});
EOF
);
// For Wizard helper to process the ajax replies
$this->add('<div id="ajax_content"></div>');
// Customize the logo (unless a customer CSS has been defined)
if ($sAlternateStyleSheet == '')
{
if (file_exists(MODULESROOT.'branding/portal-logo.png'))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/portal-logo.png';
$this->add_style("div#portal #logo {background: url(\"$sDisplayIcon\") no-repeat scroll 0 0 transparent;}");
}
}
}
public function SetCurrentTab($sTabLabel = '')
@@ -273,7 +351,7 @@ EOF
$sMenu = '';
if ($this->m_bEnableDisconnectButton)
{
$this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php'); // This menu is always present and is the last one
$this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff'); // This menu is always present and is the last one
}
foreach($this->m_aMenuButtons as $aMenuItem)
{
@@ -301,7 +379,7 @@ EOF
$sUniqueId = $sClass.$this->GetUniqueId();
$this->add("<div id=\"$sUniqueId\">\n"); // The id here MUST be the same as currentId, otherwise the pagination will be broken
cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => $sUniqueId, 'menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)));
cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => $sUniqueId, 'menu' => false, 'toolkit_menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)));
$this->add("</div>\n");
}
else
@@ -397,7 +475,7 @@ EOF
}
$oObjSearch->AddCondition_ReferencedBy($oLinkSet->GetFilter(), $sRemoteAttCode);
$aExtraParams = array('menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList));
$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList));
$oBlock = new DisplayBlock($oObjSearch, 'list', false);
$oBlock->Display($this, 1, $aExtraParams);
}
@@ -407,8 +485,7 @@ EOF
}
}
protected function DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, $sFieldName = null)
protected function DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, $sFieldName = null, $aFilterParams = array())
{
if (is_null($sFieldName))
{
@@ -439,7 +516,7 @@ EOF
{
throw new Exception("Attribute specification '$sAttSpec', '$sAttCode' should be either a link set or an external key");
}
$this->DisplaySearchField($sTargetClass, $sSubSpec, $aExtraParams, $sPrefix, $sFieldName);
$this->DisplaySearchField($sTargetClass, $sSubSpec, $aExtraParams, $sPrefix, $sFieldName, $aFilterParams);
}
else
{
@@ -453,7 +530,24 @@ EOF
if ($oAttDef->IsExternalKey())
{
$sTargetClass = $oAttDef->GetTargetClass();
$oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass));
$sFilterDefName = 'PORTAL_TICKETS_SEARCH_FILTER_'.$sAttSpec;
if (defined($sFilterDefName))
{
try
{
$oFitlerWithParams = DBObjectSearch::FromOQL(constant($sFilterDefName));
$sFilterOQL = $oFitlerWithParams->ToOQL(true, $aFilterParams);
$oAllowedValues = new DBObjectSet(DBObjectSearch::FromOQL($sFilterOQL), array(), $aFilterParams);
}
catch(OQLException $e)
{
throw new Exception("Incorrect filter '$sFilterDefName' for attribute '$sAttcode': ".$e->getMessage());
}
}
else
{
$oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass));
}
$iFieldSize = $oAttDef->GetMaxSize();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -473,7 +567,8 @@ EOF
if (is_null($aAllowedValues))
{
// Any value is possible, display an input box
$this->add("<label>".MetaModel::GetFilterLabel($sClass, $sAttSpec).":</label>&nbsp;<input class=\"textSearch\" name=\"$sPrefix$sFieldName\" value=\"$sFilterValue\"/>\n");
$sSanitizedValue = htmlentities($sFilterValue, ENT_QUOTES, 'UTF-8');
$this->add("<label>".MetaModel::GetFilterLabel($sClass, $sAttSpec).":</label>&nbsp;<input class=\"textSearch\" name=\"$sPrefix$sFieldName\" value=\"$sSanitizedValue\"/>\n");
}
else
{
@@ -510,9 +605,30 @@ EOF
}
}
/**
* Get The organization of the current user (i.e. the organization of its contact)
* @throws Exception
*/
function GetUserOrg()
{
$oOrg = null;
$iContactId = UserRights::GetContactId();
$oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => Can fail
if (is_object($oContact))
{
$oOrg = MetaModel::GetObject('Organization', $oContact->Get('org_id'), false); // false => can fail
}
else
{
throw new Exception(Dict::S('Portal:ErrorNoContactForThisUser'));
}
return $oOrg;
}
public function DisplaySearchForm($sClass, $aAttList, $aExtraParams, $sPrefix, $bClosed = true)
{
$oUserOrg = $this->GetUserOrg();
$aFilterParams = array('org_id' => $oUserOrg->GetKey(), 'contact_id' => UserRights::GetContactId());
$sCSSClass = ($bClosed) ? 'DrawerClosed' : '';
$this->add("<div id=\"ds_$sPrefix\" class=\"SearchDrawer $sCSSClass\">\n");
$this->add_ready_script(
@@ -529,13 +645,17 @@ EOF
foreach($aAttList as $sAttSpec)
{
//$oAppContext->Reset($sAttSpec); // Make sure the same parameter will not be passed twice
$this->DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix);
$this->DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, null, $aFilterParams);
}
$this->add("</p>\n");
$this->add("<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n");
foreach($aExtraParams as $sName => $sValue)
{
$this->add("<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n");
// Note: use DumpHiddenParams() to transmit arrays as hidden params
if (is_scalar($sValue))
{
$this->add("<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n");
}
}
// $this->add($oAppContext->GetForForm());
$this->add("</form>\n");
@@ -690,6 +810,17 @@ EOF
$this->p("<h1>".Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())."</h1>\n");
}
$bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($bLockEnabled)
{
// Release the concurrent lock, if any
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
if ($sOwnershipToken !== null)
{
// We're done, let's release the lock
iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken);
}
}
}
/**
@@ -731,7 +862,7 @@ EOF
}
}
$oObj = MetaModel::GetObject($sClass, $iId, false);
$oObj = MetaModel::GetObject($sClass, $iId, false);
if (!is_object($oObj))
{
throw new Exception("Could not find the object $sClass/$iId");
@@ -761,7 +892,7 @@ EOF
}
$sStepHistory = implode(',', $aPreviousSteps);
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"$sStepHistory\">");
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"".htmlentities($sStepHistory, ENT_QUOTES, 'UTF-8')."\">");
if (!is_null($sNextStep))
{
@@ -784,7 +915,10 @@ EOF
}
if ($iButtonFlags & BUTTON_BACK)
{
$aButtons[] = "<input id=\"btn_back\" type=\"submit\" value=\"".Dict::S('UI:Button:Back')."\" onClick=\"GoBack('{$this->m_sWizardId}');\">";
if (utils::ReadParam('step_back', 1) != 1)
{
$aButtons[] = "<input id=\"btn_back\" type=\"submit\" value=\"".Dict::S('UI:Button:Back')."\" onClick=\"GoBack('{$this->m_sWizardId}');\">";
}
}
if ($iButtonFlags & BUTTON_NEXT)
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -22,7 +22,7 @@
* Application internal events
* There is also a file log
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -47,13 +47,13 @@ abstract class Query extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'fields')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
@@ -82,26 +82,59 @@ class QueryOQL extends Query
MetaModel::Init_SetZListItems('details', array('name', 'description', 'oql', 'fields')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields', 'oql')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
$aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
if (!$bEditMode)
{
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
$sOql = $this->Get('oql');
$oSearch = DBObjectSearch::FromOQL($sOql);
$aParameters = $oSearch->GetQueryParams();
foreach($aParameters as $sParam => $val)
$sFields = trim($this->Get('fields'));
$bExportV1Recommended = ($sFields == '');
if ($bExportV1Recommended)
{
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
$oFieldAttDef = MetaModel::GetAttributeDef('QueryOQL', 'fields');
$oPage->add('<div class="message message_error" style="padding-left: 30px;"><div style="padding: 10px;">'.Dict::Format('UI:Query:UrlV1', $oFieldAttDef->GetLabel()).'</div></div>');
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
}
else
{
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&date_format='.urlencode((string)AttributeDateTime::GetFormat()).'&query='.$this->GetKey();
}
$sOql = $this->Get('oql');
$sMessage = null;
try
{
$oSearch = DBObjectSearch::FromOQL($sOql);
$aParameters = $oSearch->GetQueryParams();
foreach($aParameters as $sParam => $val)
{
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
}
$oPage->p(Dict::S('UI:Query:UrlForExcel').':<br/><textarea cols="80" rows="3" READONLY>'.$sUrl.'</textarea>');
if (count($aParameters) == 0)
{
$oBlock = new DisplayBlock($oSearch, 'list');
$aExtraParams = array(
//'menu' => $sShowMenu,
'table_id' => 'query_preview_'.$this->getKey(),
);
$sBlockId = 'block_query_preview_'.$this->GetKey(); // make a unique id (edition occuring in the same DOM)
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
}
}
catch (OQLException $e)
{
$sMessage = '<div class="message message_error" style="padding-left: 30px;"><div style="padding: 10px;">'.Dict::Format('UI:RunQuery:Error', $e->getHtmlDesc()).'</div></div>';
$oPage->p($sMessage);
}
$oPage->p(Dict::S('UI:Query:UrlForExcel').':<br/><textarea cols="80" rows="3" READONLY>'.$sUrl.'</textarea>');
}
return $aFieldsMap;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* Persistent class Shortcut and derived
* Shortcuts of any kind
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -142,6 +142,7 @@ EOF
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
return array();
}
// End of the minimal implementation of iDisplay
}
@@ -165,6 +166,8 @@ class ShortcutOQL extends Shortcut
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("oql", array("allowed_values"=>null, "sql"=>"oql", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("auto_reload", array("allowed_values"=>new ValueSetEnum('none,custom'), "sql"=>"auto_reload", "default_value"=>"none", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("auto_reload_sec", array("allowed_values"=>null, "sql"=>"auto_reload_sec", "default_value"=>60, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'context', 'oql')); // Attributes to be displayed for the complete details
@@ -178,11 +181,26 @@ class ShortcutOQL extends Shortcut
{
$oPage->set_title($this->Get('name'));
switch($this->Get('auto_reload'))
{
case 'custom':
$iRate = (int)$this->Get('auto_reload_sec');
if ($iRate > 0)
{
// Must a string otherwise it can be evaluated to 'true' and defaults to "standard" refresh rate!
$aExtraParams['auto_reload'] = (string)$iRate;
}
break;
default:
case 'none':
}
$bSearchPane = true;
$bSearchOpen = false;
try
{
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams);
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true);
}
catch (Exception $e)
{
@@ -191,7 +209,18 @@ class ShortcutOQL extends Shortcut
}
public static function GetCreationForm($sOQL = null)
public function CloneTableSettings($sTableSettings)
{
$aTableSettings = json_decode($sTableSettings, true);
$oFilter = DBObjectSearch::FromOQL($this->Get('oql'));
$oCustomSettings = new DataTableSettings($oFilter->GetSelectedClasses());
$oCustomSettings->iDefaultPageSize = $aTableSettings['iPageSize'];
$oCustomSettings->aColumns = $aTableSettings['oColumns'];
$oCustomSettings->Save('shortcut_'.$this->GetKey());
}
public static function GetCreationForm($sOQL = null, $sTableSettings = null)
{
$oForm = new DesignerForm();
@@ -214,20 +243,36 @@ class ShortcutOQL extends Shortcut
$oField = new DesignerTextField('name', Dict::S('Class:Shortcut/Attribute:name'), $sDefault);
$oField->SetMandatory(true);
$oForm->AddField($oField);
//$oField = new DesignerLongTextField('oql', Dict::S('Class:Shortcut/Attribute:oql'), $sOQL);
//$oField->SetMandatory();
/*
$oField = new DesignerComboField('auto_reload', Dict::S('Class:ShortcutOQL/Attribute:auto_reload'), 'none');
$oAttDef = MetaModel::GetAttributeDef(__class__, 'auto_reload');
$oField->SetAllowedValues($oAttDef->GetAllowedValues());
$oField->SetMandatory(true);
$oForm->AddField($oField);
*/
$oField = new DesignerBooleanField('auto_reload', Dict::S('Class:ShortcutOQL/Attribute:auto_reload'), false);
$oForm->AddField($oField);
$oField = new DesignerIntegerField('auto_reload_sec', Dict::S('Class:ShortcutOQL/Attribute:auto_reload_sec'), MetaModel::GetConfig()->GetStandardReloadInterval());
$oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit
$oField->SetMandatory(false);
$oForm->AddField($oField);
$oField = new DesignerHiddenField('oql', '', $sOQL);
$oForm->AddField($oField);
$oField = new DesignerHiddenField('table_settings', '', $sTableSettings);
$oForm->AddField($oField);
return $oForm;
}
public static function GetCreationDlgFromOQL($oPage, $sOQL)
public static function GetCreationDlgFromOQL($oPage, $sOQL, $sTableSettings)
{
$oPage->add('<div id="shortcut_creation_dlg">');
$oForm = self::GetCreationForm($sOQL);
$oForm = self::GetCreationForm($sOQL, $sTableSettings);
$oForm->Render($oPage);
$oPage->add('</div>');
@@ -239,9 +284,19 @@ class ShortcutOQL extends Shortcut
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sRateTitle = addslashes(Dict::Format('Class:ShortcutOQL/Attribute:auto_reload_sec/tip', MetaModel::GetConfig()->Get('min_reload_interval')));
$oPage->add_ready_script(
<<<EOF
// Note: the title gets deleted by the validation mechanism
$("#attr_auto_reload_sec").tooltip({items: 'input', content: '$sRateTitle'});
$("#attr_auto_reload_sec").prop('disabled', !$('#attr_auto_reload').is(':checked'));
$('#attr_auto_reload').change( function(ev) {
$("#attr_auto_reload_sec").prop('disabled', !$(this).is(':checked'));
} );
function ShortcutCreationOK()
{
var oForm = $('#shortcut_creation_dlg form');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,18 +20,20 @@
/**
* File to include to initialize the datamodel in memory
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
session_name('itop-'.md5(APPROOT));
session_start();
if (isset($_REQUEST['switch_env']))
$sSwitchEnv = utils::ReadParam('switch_env', null);
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
{
$sEnv = $_REQUEST['switch_env'];
$_SESSION['itop_env'] = $sEnv;
$_SESSION['itop_env'] = $sSwitchEnv;
$sEnv = $sSwitchEnv;
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))
@@ -44,6 +46,4 @@ else
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile);
?>
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);

View File

@@ -1,20 +0,0 @@
<div class="page_header" style="padding:0.5em;">
<h1><itopstring>UI:NotificationsMenu:Title</itopstring></h1>
</div>
<itoptoggle name="UI:NotificationsMenu:Help" open="true">
<div style="padding: 1em; font-size:10pt;background:#E8F3CF;margin-top: 0.25em;">
<img src="../images/bell.png" style="margin-top: -60px; margin-right: 10px; float: right;">
<itopstring>UI:NotificationsMenu:HelpContent</itopstring>
</div>
</itoptoggle>
<p>&nbsp;</p>
<itoptabs>
<itoptab name="UI:NotificationsMenu:Triggers">
<h2><itopstring>UI:NotificationsMenu:AvailableTriggers</itopstring></h2>
<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT Trigger</itopblock>
</itoptab>
<itoptab name="UI:NotificationsMenu:Actions">
<h2><itopstring>UI:NotificationsMenu:AvailableActions</itopstring></h2>
<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT ActionEmail</itopblock>
</itoptab>
</itoptabs>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,8 @@
/**
* This class records the pending "transactions" corresponding to forms that have not been
* submitted yet, in order to prevent double submissions. When created a transaction remains valid
* until the user's session expires
* until the user's session expires. This class is actually a wrapper to the underlying implementation
* which choice is configured via the parameter 'transaction_storage'
*
* @package iTop
* @copyright Copyright (C) 2010-2012 Combodo SARL
@@ -28,6 +29,81 @@
class privUITransaction
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return 'notransactions'; // Any value will do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions.");
$sClass = 'privUITransactionSession';
}
return (string)$sClass::GetNewTransactionId();
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return true; // All values are valid
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
return $sClass::IsTransactionValid($id, $bRemoveTransaction);
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTranscationId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return; // Nothing to do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
$sClass::RemoveTransaction($id);
}
}
/**
* The original (and by default) mechanism for storing transaction information
* as an array in the $_SESSION variable
*
*/
class privUITransactionSession
{
/**
* Create a new transaction id, store it in the session and return its id
@@ -99,4 +175,178 @@ class privUITransaction
}
}
}
?>
/**
* An alternate implementation for storing the transactions as temporary files
* Useful when using an in-memory storage for the session which do not
* guarantee mutual exclusion for writing
*/
class privUITransactionFile
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
if (!is_dir(APPROOT.'data/transactions'))
{
if (!is_writable(APPROOT.'data'))
{
throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.');
}
if (!@mkdir(APPROOT.'data/transactions'))
{
throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
}
}
if (!is_writable(APPROOT.'data/transactions'))
{
throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.');
}
self::CleanupOldTransactions();
$id = basename(tempnam(APPROOT.'data/transactions', self::GetUserPrefix()));
self::Info('GetNewTransactionId: Created transaction: '.$id);
return (string)$id;
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
$bResult = file_exists($sFilepath);
if ($bResult)
{
if ($bRemoveTransaction)
{
$bResult = @unlink($sFilepath);
if (!$bResult)
{
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
}
else
{
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
}
}
}
else
{
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
return $bResult;
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bSuccess = true;
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
if(!file_exists($sFilepath))
{
$bSuccess = false;
self::Error("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
$bSuccess = @unlink($sFilepath);
if (!$bSuccess)
{
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
}
else
{
self::Info('RemoveTransaction: OK '.$id);
}
return $bSuccess;
}
/**
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions()
{
$iLimit = time() - 24*3600;
clearstatcache();
$aTransactions = glob(APPROOT.'data/transactions/*-*');
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)
{
@unlink($sFileName);
self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName);
}
}
}
/**
* For debugging purposes: gets the pending transactions of the current user
* as an array, with the date of the creation of the transaction file
*/
protected static function GetPendingTransactions()
{
clearstatcache();
$aResult = array();
$aTransactions = glob(APPROOT.'data/transactions/'.self::GetUserPrefix().'*');
foreach($aTransactions as $sFileName)
{
$aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName);
}
sort($aResult);
return $aResult;
}
protected static function GetUserPrefix()
{
$sPrefix = substr(UserRights::GetUser(), 0, 10);
$sPrefix = preg_replace('/[^a-zA-Z0-9-_]/', '_', $sPrefix);
return $sPrefix.'-';
}
protected static function Info($sText)
{
self::Write('Info | '.$sText);
}
protected static function Warning($sText)
{
self::Write('Warning | '.$sText);
}
protected static function Error($sText)
{
self::Write('Error | '.$sText);
}
protected static function Write($sText)
{
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
if ($bLogEnabled)
{
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -54,7 +54,7 @@
* | | +--------+ +-----+ | |
* | +--------------------------------------------+ |
* +------------------------------------------------+
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -149,7 +149,7 @@ class UIExtKeyWidget
case 'radio':
case 'radio_horizontal':
case 'radio_vertical':
$sValidationField = "<span id=\"v_{$this->iId}\"></span>";
$sValidationField = "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
$sHTMLValue = '';
$bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false;
@@ -164,6 +164,7 @@ class UIExtKeyWidget
break;
case 'select':
case 'list':
default:
$sSelectMode = 'true';
@@ -207,7 +208,16 @@ class UIExtKeyWidget
$sHTMLValue .= "</select>\n";
if (($this->bSearchMode) && $bSearchMultiple)
{
$oPage->add_ready_script("$('.multiselect').multiselect({header: false, noneSelectedText: '".addslashes(Dict::S('UI:SearchValue:Any'))."', selectedList: 1, selectedText:'".addslashes(Dict::S('UI:SearchValue:NbSelected'))."'});");
$aOptions = array(
'header' => true,
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
'selectedList' => 1,
);
$sJSOptions = json_encode($aOptions);
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
}
$oPage->add_ready_script(
<<<EOF
@@ -243,14 +253,14 @@ EOF
$sDisplayValue = $this->GetObjectName($value);
}
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 30; //@@@ $this->oAttDef->GetMaxSize();
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
// the input for the auto-complete
$sHTMLValue = "<input count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif\" onClick=\"oACWidget_{$this->iId}.Search();\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/>";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n";
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it
@@ -271,7 +281,7 @@ EOF
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/>&nbsp;";
$oPage->add_ready_script(
<<<EOF
if ($('#ac_tree_{$this->iId}').length == 0)
@@ -283,7 +293,7 @@ EOF
}
if ($bCreate && $bExtensions)
{
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/>&nbsp;";
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/>&nbsp;";
$oPage->add_ready_script(
<<<EOF
if ($('#ajax_{$this->iId}').length == 0)
@@ -293,9 +303,9 @@ EOF
EOF
);
}
if ($sDisplayStyle == 'select')
if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
{
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span><span id=\"fstatus_{$this->iId}\"></span>";
}
$sHTMLValue .= "</span>"; // end of no wrap
return $sHTMLValue;
@@ -308,8 +318,9 @@ EOF
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = array('query_params' => array('this' => $oCurrObject));
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$aArgs = array('this' => $oCurrObject);
$aParams = array('query_params' => $aArgs);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs);
$oFilter = $oSet->GetFilter();
}
else
@@ -317,9 +328,10 @@ EOF
$aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
@@ -440,10 +452,20 @@ EOF
$sDialogTitle = '';
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
$aFieldsFlags = array();
$aFieldsComments = array();
foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef)
{
if (($oAttDef instanceof AttributeBlob) || (false))
{
$aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
$aFieldsComments[$sAttCode] = '&nbsp;<img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
}
}
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
$oPage->add('</div></div></div>');
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);");
}
@@ -494,16 +516,23 @@ EOF
*/
public function DoCreateObject($oPage)
{
$oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0)
try
{
$oObj->DBInsert();
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
$oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0)
{
$oObj->DBInsert();
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
}
else
{
return array('error' => implode(' ', $aErrors), 'id' => 0);
}
}
else
catch(Exception $e)
{
return array('name' => implode(' ', $aErrors), 'id' => 0);
return array('error' => $e->getMessage(), 'id' => 0);
}
}
@@ -547,6 +576,7 @@ EOF
$aSortedRoots = $aTree[$iRootId];
asort($aSortedRoots);
$oP->add("<ul>\n");
$fUniqueId = microtime(true);
foreach($aSortedRoots as $id => $sName)
{
if ($bSelect)
@@ -554,14 +584,14 @@ EOF
$sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : '';
if ($bMultiple)
{
$sSelect = '<input type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;';
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;';
}
else
{
$sSelect = '<input type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
}
}
$oP->add('<li>'.$sSelect.$aNodes[$id]->GetHyperlink());
$oP->add('<li>'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>');
$this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue);
$oP->add("</li>\n");
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,13 +21,15 @@
* UI wdiget for displaying and editing one-way encrypted passwords
*
* @author Phil Eddies
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @author Romain Quetiez
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class UIHTMLEditorWidget
{
protected $m_iId;
protected $m_oAttDef;
protected $m_sAttCode;
protected $m_sNameSuffix;
protected $m_sFieldPrefix;
@@ -36,10 +38,11 @@ class UIHTMLEditorWidget
protected $m_sValue;
protected $m_sMandatory;
public function __construct($iInputId, $sAttCode, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $sValue, $sMandatory)
public function __construct($iInputId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $sValue, $sMandatory)
{
$this->m_iId = $iInputId;
$this->m_sAttCode = $sAttCode;
$this->m_oAttDef = $oAttDef;
$this->m_sAttCode = $oAttDef->GetCode();
$this->m_sNameSuffix = $sNameSuffix;
$this->m_sHelpText = $sHelpText;
$this->m_sValidationField = $sValidationField;
@@ -68,8 +71,24 @@ class UIHTMLEditorWidget
// To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor()
$aConfig = array();
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, { language : '$sLanguage' , contentsLanguage : '$sLanguage', extraPlugins: 'disabler' });"); // Transform $iId into a CKEdit
$aConfig['language'] = $sLanguage;
$aConfig['contentsLanguage'] = $sLanguage;
$aConfig['extraPlugins'] = 'disabler';
$sWidthSpec = addslashes(trim($this->m_oAttDef->GetWidth()));
if ($sWidthSpec != '')
{
$aConfig['width'] = $sWidthSpec;
}
$sHeightSpec = addslashes(trim($this->m_oAttDef->GetHeight()));
if ($sHeightSpec != '')
{
$aConfig['height'] = $sHeightSpec;
}
$sConfigJS = json_encode($aConfig);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
// Please read...
// ValidateCKEditField triggers a timer... calling itself indefinitely
@@ -80,7 +99,7 @@ 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')); } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
return $sHtmlValue;
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Class UILinksWidgetDirect
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -46,14 +46,24 @@ class UILinksWidgetDirect
$oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$this->sLinkedClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list'));
switch($oLinksetDef->GetEditMode())
{
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'details'));
break;
default:
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list'));
array_unshift($aZList, 'friendlyname');
}
foreach($aZList as $sLinkedAttCode)
{
if ($sLinkedAttCode != $sExtKeyToMe)
{
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
if (!$oAttDef->IsExternalField() || ($oAttDef->GetKeyAttCode() != $sExtKeyToMe) )
if ((!$oAttDef->IsExternalField() || ($oAttDef->GetKeyAttCode() != $sExtKeyToMe)) &&
(!$oAttDef->IsLinkSet()) )
{
$this->aZlist[] = $sLinkedAttCode;
}
@@ -79,7 +89,7 @@ class UILinksWidgetDirect
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams&{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
}
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
break;
@@ -88,6 +98,18 @@ class UILinksWidgetDirect
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj);
break;
case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place'
$sTargetClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$oExtKeyDef = MetaModel::GetAttributeDef($sTargetClass, $sExtKeyToMe);
$aButtons = array('add');
if ($oExtKeyDef->IsNullAllowed())
{
$aButtons = array('add', 'remove');
}
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons);
break;
case LINKSET_EDITMODE_ACTIONS:
default:
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
@@ -130,7 +152,7 @@ class UILinksWidgetDirect
}
}
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
{
$aAttribs = $this->GetTableConfig();
@@ -156,10 +178,16 @@ class UILinksWidgetDirect
// 'modify' => 'Modify...' ,
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
'remove' => Dict::S('UI:Button:Remove'),
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
);
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$oContext = new ApplicationContext();
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
$sJSONLabels = json_encode($aLabels);
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, sumit_to: '$sSubmitUrl' });");
$sJSONButtons = json_encode($aButtons);
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });");
}
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
@@ -212,6 +240,104 @@ class UILinksWidgetDirect
$oPage->add('</div></div>');
}
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
{
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$valuesDef = $oLinksetDef->GetValuesDef();
if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($this->sLinkedClass);
}
else
{
if (!$valuesDef instanceof ValueSetObjects)
{
throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').');
}
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
}
if ($oCurrentObj != null)
{
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
}
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->sInputid}\" value=\"0\"/>";
$sHtml .= "<button type=\"button\" class=\"cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;<button type=\"button\" class=\"ok\" disabled=\"disabled\">".Dict::S('UI:Button:Add')."</button>";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$oPage->add($sHtml);
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->sInputId}.SearchObjectsToAdd);");
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix}').resize(oWidget{$this->siInputId}.UpdateSizes);");
}
/**
* Search for objects to be linked to the current object (i.e "remote" objects)
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of $this->sLinkedClass
* @param array $aAlreadyLinked Array of indentifiers of objects which are already linke to the current object (or about to be linked)
* @param DBObject $oCurrentObj The object currently being edited... if known...
*/
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null)
{
if ($sRemoteClass == '')
{
$sRemoteClass = $this->sLinkedClass;
}
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$valuesDef = $oLinksetDef->GetValuesDef();
if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($sRemoteClass);
}
else
{
if (!$valuesDef instanceof ValueSetObjects)
{
throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').');
}
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
}
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass))
{
// Prevent linking to self if the linked object is of the same family
// and laready present in the database
if (!$oCurrentObj->IsNew())
{
$oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
}
}
if (count($aAlreadyLinked) > 0)
{
$oFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN');
}
if ($oCurrentObj != null)
{
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
$oFilter->SetInternalParams($aArgs);
}
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", array('menu' => false, 'cssCount'=> '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $oFullSetFilter)
{
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkObj = MetaModel::GetObject($this->sLinkedClass, $iObjectId);
$oP->add($this->GetObjectRow($oP, $oLinkObj, $oLinkObj->GetKey()));
}
}
public function GetObjectModificationDlg()
{
@@ -229,9 +355,9 @@ class UILinksWidgetDirect
}
return $aAttribs;
}
public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
{
$aAttribs = $this->GetTableConfig();
if ($sRealClass == '')
{
$sRealClass = $this->sLinkedClass;
@@ -239,17 +365,60 @@ class UILinksWidgetDirect
$oLinkObj = new $sRealClass();
$oLinkObj->UpdateObjectFromPostedForm($this->sInputid);
return $this->GetObjectRow($oPage, $oLinkObj, $iTempId);
}
protected function GetObjectRow($oPage, $oLinkObj, $iTempId)
{
$aAttribs = $this->GetTableConfig();
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.(-$iTempId).'"/>';
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>';
foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
return $oPage->GetTableRow($aRow, $aAttribs);
return $oPage->GetTableRow($aRow, $aAttribs);
}
public function UpdateFromArray($oObj, $aData)
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{
$oAppContext = new ApplicationContext();
$sSrcClass = get_class($oSourceObj);
$sDestClass = $oSearch->GetClass();
foreach($oAppContext->GetNames() as $key)
{
// Find the value of the object corresponding to each 'context' parameter
$aCallSpec = array($sSrcClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
$defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class
// and sets its value as the default value for the search condition
$aCallSpec = array($sDestClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
{
$oSearch->AddCondition($sAttCode, $defaultValue);
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class UILinksWidget
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -106,7 +106,7 @@ class UILinksWidget
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
$aRow = array();
$aFieldsMap = array();
if(is_object($linkObjOrId))
if(is_object($linkObjOrId) && (!$linkObjOrId->IsNew()))
{
$key = $linkObjOrId->GetKey();
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
@@ -130,12 +130,24 @@ class UILinksWidget
else
{
// form for creating a new record
if (is_object($linkObjOrId))
{
// New link existing only in memory
$oNewLinkObj = $linkObjOrId;
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$linkObjOrId = -$iRemoteObjKey;
}
else
{
$iRemoteObjKey = -$linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
}
$sPrefix .= "[$linkObjOrId][";
$iRemoteObjKey = -$linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
@@ -151,6 +163,12 @@ 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({
@@ -160,8 +178,11 @@ $(".date-pick").datepicker({
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
@@ -169,7 +190,10 @@ $(".datetime-pick").datepicker({
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true
changeYear: true,
dayNamesMin: $sJSDaysMin,
monthNamesShort: $sJSMonthsShort,
firstDay: $iFirstDayOfWeek
});
EOF
);
@@ -227,7 +251,7 @@ EOF
*/
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
{
$sHtml = '';
$sHtml = "<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">";
$sHtml .= "<table class=\"listResults\">\n";
// Header
$sHtml .= "<thead>\n";
@@ -247,11 +271,11 @@ EOF
$sEmptyRowStyle = 'style="display:none;"';
}
$sHtml .= "<tr $sEmptyRowStyle id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"></td></td>";
foreach($aData as $iRowId => $aRow)
{
$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
$sHtml .= "<tr $sEmptyRowStyle id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."</td></tr>";
$sHtml .= "</tbody>\n";
// Footer
@@ -275,6 +299,7 @@ EOF
$sHtmlValue = '';
$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
$sHtmlValue .= "<div id=\"linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
$sHtmlValue .= "<input type=\"hidden\" id=\"{$sFormPrefix}{$this->m_iInputId}\">\n";
$oValue->Rewind();
$aForm = array();
while($oCurrentLink = $oValue->Fetch())
@@ -284,7 +309,7 @@ EOF
if ($oCurrentLink->IsNew())
{
$key = -$oLinkedObj->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $key, $aArgs, $oCurrentObj);
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
}
else
{
@@ -297,8 +322,9 @@ EOF
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script(<<<EOF
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper);
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}');
oWidget{$this->m_iInputId}.Init();
$('#{$this->m_iInputId}').bind('update_value', function() { $(this).val(oWidget{$this->m_iInputId}.GetUpdatedValue()); })
EOF
);
$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
@@ -330,11 +356,12 @@ EOF
public function GetObjectPickerDialog($oPage, $oCurrentObj)
{
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => $bOpen));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
@@ -426,7 +453,7 @@ EOF
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBObjectSearch $oSearch
* @param DBSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{

View File

@@ -52,11 +52,13 @@ class UIPasswordWidget
{
$sCode = $this->sAttCode.$this->sNameSuffix;
$iWidgetIndex = self::$iWidgetIndex;
$sPasswordValue = utils::ReadPostedParam("attr_{$sCode}[value]", '*****', 'raw_data');
$sConfirmPasswordValue = utils::ReadPostedParam("attr_{$sCode}[confirm]", '*****', 'raw_data');
$aPasswordValues = utils::ReadPostedParam("attr_{$sCode}", null, 'raw_data');
$sPasswordValue = $aPasswordValues ? $aPasswordValues['value'] : '*****';
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><span id="fstatus_'.$this->iId.'"></span><br/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';

View File

@@ -275,7 +275,7 @@ EOF
$this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm);
$oP->add("<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"RemoveSelected();\" >");
$oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sLinkedClass))."\" onClick=\"AddObjects();\"></span>\n");
$oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"BackToDetails('".$sTargetClass."', ".$this->m_iObjectId.");\">");
$oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"BackToDetails('".$sTargetClass."', ".$this->m_iObjectId.", '', '');\">");
$oP->add("&nbsp;&nbsp;&nbsp;<input id=\"btnOk\" type=\"submit\" value=\"".Dict::S('UI:Button:Ok')."\"></span>\n");
$oP->add("<span style=\"clear:both;\"><p>&nbsp;</p></span>\n");
$oP->add("</div>\n");

View File

@@ -130,7 +130,7 @@ class appUserPreferences extends DBObject
/**
* Call this function if the user has changed (like when doing a logoff...)
*/
static public function Reset()
static public function ResetPreferences()
{
self::$oUserPrefs = null;
}

View File

@@ -1,5 +1,7 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
use Html2Text\Html2Text;
use Leafo\ScssPhp\Compiler;
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,12 +22,14 @@
/**
* Static class utils
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/application/transaction.class.inc.php');
require_once(APPROOT.'application/Html2Text.php');
require_once(APPROOT.'application/Html2TextException.php');
define('ITOP_CONFIG_FILE', 'config-itop.php');
define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
@@ -284,7 +288,7 @@ class utils
$rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
if ($rInfo !== false)
{
$sType = @finfo_file($rInfo, $file);
$sType = @finfo_file($rInfo, $sTmpName);
if ( ($sType !== false)
&& is_string($sType)
&& (strlen($sType)>0))
@@ -334,7 +338,7 @@ class utils
/**
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
* @param $oFullSetFilter DBObjectSearch The criteria defining the whole sets of objects being selected
* @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
* @return Array An arry of object IDs corresponding to the objects selected in the set
*/
public static function ReadMultipleSelection($oFullSetFilter)
@@ -383,7 +387,23 @@ class utils
{
return privUITransaction::RemoveTransaction($sId);
}
/**
* Returns a unique tmp id for the current upload based on the transaction system (db).
*
* Build as session_id() . '_' . static::GetNewTransactionId()
*
* @return string
*/
public static function GetUploadTempId($sTransactionId = null)
{
if ($sTransactionId === null)
{
$sTransactionId = static::GetNewTransactionId();
}
return session_id() . '_' . $sTransactionId;
}
public static function ReadFromFile($sFileName)
{
if (!file_exists($sFileName)) return false;
@@ -463,6 +483,19 @@ class utils
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Convert an old date/time format specifciation (using % placeholders)
* to a format compatible with DateTime::createFromFormat
* @param string $sOldDateTimeFormat
* @return string
*/
static public function DateTimeFormatToPHP($sOldDateTimeFormat)
{
$aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
$aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
}
static public function GetConfig()
{
@@ -487,19 +520,27 @@ class utils
*/
static public function GetAbsoluteUrlAppRoot()
{
$sUrl = self::GetConfig()->Get('app_root_url');
if (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
static $sUrl = null;
if ($sUrl === null)
{
if (isset($_SERVER['SERVER_NAME']))
$sUrl = self::GetConfig()->Get('app_root_url');
if ($sUrl == '')
{
$sServerName = $_SERVER['SERVER_NAME'];
$sUrl = self::GetDefaultUrlAppRoot();
}
else
elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
{
// CLI mode ?
$sServerName = php_uname('n');
if (isset($_SERVER['SERVER_NAME']))
{
$sServerName = $_SERVER['SERVER_NAME'];
}
else
{
// CLI mode ?
$sServerName = php_uname('n');
}
$sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
}
$sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
}
return $sUrl;
}
@@ -508,7 +549,7 @@ class utils
{
// Build an absolute URL to this page on this server/port
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$sProtocol = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!="off")) ? 'https' : 'http';
$sProtocol = self::IsConnectionSecure() ? 'https' : 'http';
$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($sProtocol == 'http')
{
@@ -571,6 +612,25 @@ class utils
return $sAppRootUrl;
}
/**
* Helper to handle the variety of HTTP servers
* See #286 (fixed in [896]), and #634 (this fix)
*
* Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' !
* nginx set it to an empty string
* Others might leave it unset (no array entry)
*/
static public function IsConnectionSecure()
{
$bSecured = false;
if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
{
$bSecured = true;
}
return $bSecured;
}
/**
* Tells whether or not log off operation is supported.
* Actually in only one case:
@@ -745,35 +805,110 @@ class utils
return ITOP_DEFAULT_ENV;
}
}
/**
* Get the "Back" button to go out of the current environment
* Returns a path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation
* @return string
*/
public static function GetEnvironmentBackButton()
public static function GetCachePath()
{
if (isset($_SESSION['itop_return_env']))
{
if (isset($_SESSION['itop_return_url']))
{
$sReturnUrl = $_SESSION['itop_return_url'];
}
else
{
$sReturnUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?switch_env='.$_SESSION['itop_return_env'];
}
return '&nbsp;<button onclick="window;location.href=\''.addslashes($sReturnUrl).'\'">'.Dict::S('UI:Button:Back').'</button>';
}
else
{
return '';
}
return APPROOT.'data/cache-'.self::GetCurrentEnvironment().'/';
}
/**
* Get the "Back" button to go out of the current environment
* Merge standard menu items with plugin provided menus items
*/
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions)
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
{
// 1st - add standard built-in menu items
//
switch($iMenuId)
{
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
// $param is a DBObjectSet
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
$sFilter = urlencode($param->GetFilter()->serialize());
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page, CSV Export & Add to Dashboard
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
);
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
{
// Bulk export actions
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
// $param is a DBObject
$oObj = $param;
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$sFilter = $oFilter->serialize();
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
);
break;
case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
// $param is a Dashboard
$oAppContext = new ApplicationContext();
$aParams = $oAppContext->GetAsHash();
$sMenuId = ApplicationMenu::GetActiveNodeId();
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
$aResult = array(
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sMenuId),
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn' })"),
);
break;
default:
// Unknown type of menu, do nothing
$aResult = array();
}
foreach($aResult as $oMenuItem)
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
}
// Invoke the plugins
//
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
if (is_object($param) && !($param instanceof DBObject))
@@ -820,6 +955,35 @@ class utils
return $sUrl;
}
/**
* Returns the URL to a page that will execute the requested module page
*
* To be compatible with this mechanism, the called page must include approot
* with an absolute path OR not include it at all (losing the direct access to the page)
* if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
* require_once(__DIR__.'/../../approot.inc.php');
*
* @return string ...
*/
static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
{
$sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment;
$aArgs = array();
$aArgs[] = 'exec_module='.$sModule;
$aArgs[] = 'exec_page='.$sPage;
$aArgs[] = 'exec_env='.$sEnvironment;
foreach($aArguments as $sName => $sValue)
{
if (($sName == 'exec_module')||($sName == 'exec_page')||($sName == 'exec_env'))
{
throw new Exception("Module page: $sName is a reserved page argument name");
}
$aArgs[] = $sName.'='.urlencode($sValue);
}
$sArgs = implode('&', $aArgs);
return self::GetAbsoluteUrlAppRoot().'pages/exec.php?'.$sArgs;
}
/**
* Returns a name unique amongst the given list
* @param string $sProposed The default value
@@ -850,6 +1014,333 @@ class utils
static public function GetSafeId($sId)
{
return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
}
/**
* Helper to execute an HTTP POST request
* Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
* originaly named after do_post_request
* Does not require cUrl but requires openssl for performing https POSTs.
*
* @param string $sUrl The URL to POST the data to
* @param hash $aData The data to POST as an array('param_name' => value)
* @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
* @param hash $aResponseHeaders An array to be filled with reponse headers: WARNING: the actual content of the array depends on the library used: cURL or fopen, test with both !! See: http://fr.php.net/manual/en/function.curl-getinfo.php
* @param hash $aCurlOptions An (optional) array of options to pass to curl_init. The format is 'option_code' => 'value'. These values have precedence over the default ones. Example: CURLOPT_SSLVERSION => CURL_SSLVERSION_SSLv3
* @return string The result of the POST request
* @throws Exception
*/
static public function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array())
{
// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
if (function_exists('curl_init'))
{
// If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options
// For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
// by setting the SSLVERSION to 3 as done below.
$aHeaders = explode("\n", $sOptionnalHeaders);
$aHTTPHeaders = array();
foreach($aHeaders as $sHeaderString)
{
if(preg_match('/^([^:]): (.+)$/', $sHeaderString, $aMatches))
{
$aHTTPHeaders[$aMatches[1]] = $aMatches[2];
}
}
// Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
$aOptions = array(
CURLOPT_RETURNTRANSFER => true, // return the content of the request
CURLOPT_HEADER => false, // don't return the headers in the output
CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_ENCODING => "", // handle all encodings
CURLOPT_USERAGENT => "spider", // who am i
CURLOPT_AUTOREFERER => true, // set referer on redirect
CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
CURLOPT_TIMEOUT => 120, // timeout on response
CURLOPT_MAXREDIRS => 10, // stop after 10 redirects
CURLOPT_SSL_VERIFYPEER => false, // Disabled SSL Cert checks
// SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why
// but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
// CURLOPT_SSLVERSION => 3,
CURLOPT_POST => count($aData),
CURLOPT_POSTFIELDS => http_build_query($aData),
CURLOPT_HTTPHEADER => $aHTTPHeaders,
);
$aAllOptions = $aCurlOptions + $aOptions;
$ch = curl_init($sUrl);
curl_setopt_array($ch, $aAllOptions);
$response = curl_exec($ch);
$iErr = curl_errno($ch);
$sErrMsg = curl_error( $ch );
$aHeaders = curl_getinfo( $ch );
if ($iErr !== 0)
{
throw new Exception("Problem opening URL: $sUrl, $sErrMsg");
}
if (is_array($aResponseHeaders))
{
$aHeaders = curl_getinfo($ch);
foreach($aHeaders as $sCode => $sValue)
{
$sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type"
$aResponseHeaders[$sName] = $sValue;
}
}
curl_close( $ch );
}
else
{
// cURL is not available let's try with streams and fopen...
$sData = http_build_query($aData);
$aParams = array('http' => array(
'method' => 'POST',
'content' => $sData,
'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
));
if ($sOptionnalHeaders !== null)
{
$aParams['http']['header'] .= $sOptionnalHeaders;
}
$ctx = stream_context_create($aParams);
$fp = @fopen($sUrl, 'rb', false, $ctx);
if (!$fp)
{
global $php_errormsg;
if (isset($php_errormsg))
{
throw new Exception("Wrong URL: $sUrl, $php_errormsg");
}
elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl'))
{
throw new Exception("Cannot connect to $sUrl: missing module 'openssl'");
}
else
{
throw new Exception("Wrong URL: $sUrl");
}
}
$response = @stream_get_contents($fp);
if ($response === false)
{
throw new Exception("Problem reading data from $sUrl, $php_errormsg");
}
if (is_array($aResponseHeaders))
{
$aMeta = stream_get_meta_data($fp);
$aHeaders = $aMeta['wrapper_data'];
foreach($aHeaders as $sHeaderString)
{
if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches))
{
$aResponseHeaders[$aMatches[1]] = trim($aMatches[2]);
}
}
}
}
return $response;
}
/**
* Get a standard list of character sets
*
* @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label
*/
public static function GetPossibleEncodings($aAdditionalEncodings = array())
{
// Encodings supported:
// ICONV_CODE => Display Name
// Each iconv installation supports different encodings
// Some reasonably common and useful encodings are listed here
$aPossibleEncodings = array(
'UTF-8' => 'Unicode (UTF-8)',
'ISO-8859-1' => 'Western (ISO-8859-1)',
'WINDOWS-1251' => 'Cyrilic (Windows 1251)',
'WINDOWS-1252' => 'Western (Windows 1252)',
'ISO-8859-15' => 'Western (ISO-8859-15)',
);
$aPossibleEncodings = array_merge($aPossibleEncodings, $aAdditionalEncodings);
asort($aPossibleEncodings);
return $aPossibleEncodings;
}
/**
* Convert a string containing some (valid) HTML markup to plain text
* @param string $sHtml
* @return string
*/
public static function HtmlToText($sHtml)
{
try
{
//return '<?xml encoding="UTF-8">'.$sHtml;
return \Html2Text\Html2Text::convert('<?xml encoding="UTF-8">'.$sHtml);
}
catch(Exception $e)
{
return $e->getMessage();
}
}
/**
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
* and escaping HTML entities
* @param string $sText
* @return string
*/
public static function TextToHtml($sText)
{
$sText = str_replace("\r\n", "\n", $sText);
$sText = str_replace("\r", "\n", $sText);
return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
}
/**
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
*
* @param string $sSassRelPath Relative path to the SCSS file (must have the extension .scss)
* @param array $aImportPaths Array of absolute paths to load imports from
* @return string Relative path to the CSS file (<name>.css)
*/
static public function GetCSSFromSASS($sSassRelPath, $aImportPaths = null)
{
// Avoiding compilation if file is already a css file.
if (preg_match('/\.css$/', $sSassRelPath))
{
return $sSassRelPath;
}
// Setting import paths
if ($aImportPaths === null)
{
$aImportPaths = array();
}
$aImportPaths[] = APPROOT . '/css';
$sSassPath = APPROOT.$sSassRelPath;
$sCssRelPath = preg_replace('/\.scss$/', '.css', $sSassRelPath);
$sCssPath = APPROOT.$sCssRelPath;
clearstatcache();
if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath))))
{
require_once(APPROOT.'lib/scssphp/scss.inc.php');
$oScss = new Compiler();
$oScss->setImportPaths($aImportPaths);
$oScss->setFormatter('Leafo\\ScssPhp\\Formatter\\Expanded');
// Temporary disabling max exec time while compiling
$iCurrentMaxExecTime = (int) ini_get('max_execution_time');
set_time_limit(0);
$sCss = $oScss->compile(file_get_contents($sSassPath));
set_time_limit($iCurrentMaxExecTime);
file_put_contents($sCssPath, $sCss);
}
return $sCssRelPath;
}
static public function GetImageSize($sImageData)
{
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
{
$aRet = @getimagesizefromstring($sImageData);
}
else if(ini_get('allow_url_fopen'))
{
// work around to avoid creating a tmp file
$sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
$aRet = @getimagesize($sUri);
}
else
{
// Damned, need to create a tmp file
$sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
@file_put_contents($sTempFile, $sImageData);
$aRet = @getimagesize($sTempFile);
@unlink($sTempFile);
}
return $aRet;
}
/**
* Resize an image attachment so that it fits in the given dimensions
* @param ormDocument $oImage The original image stored as an ormDocument
* @param int $iWidth Image's original width
* @param int $iHeight Image's original height
* @param int $iMaxImageWidth Maximum width for the resized image
* @param int $iMaxImageHeight Maximum height for the resized image
* @return ormDocument The resampled image
*/
public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
{
if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
{
return $oImage;
}
switch($oImage->GetMimeType())
{
case 'image/gif':
case 'image/jpeg':
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
return $oImage;
}
if ($img === false)
{
//throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
return $oImage;
}
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oResampledImage;
}
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class WebPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -68,10 +68,12 @@ class WebPage implements Page
protected $sContentType;
protected $sContentDisposition;
protected $sContentFileName;
protected $bTrashUnexpectedOutput;
protected $s_sOutputFormat;
protected $a_OutputOptions;
protected $bPrintable;
public function __construct($s_title)
public function __construct($s_title, $bPrintable = false)
{
$this->s_title = $s_title;
$this->s_content = "";
@@ -88,8 +90,10 @@ class WebPage implements Page
$this->sContentType = '';
$this->sContentDisposition = '';
$this->sContentFileName = '';
$this->bTrashUnexpectedOutput = false;
$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
$this->a_OutputOptions = array();
$this->bPrintable = $bPrintable;
ob_start(); // Start capturing the output
}
@@ -267,6 +271,18 @@ class WebPage implements Page
$this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition);
}
public function add_saas($sSaasRelPath)
{
$sCssRelPath = utils::GetCSSFromSASS($sSaasRelPath);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
if ($sRootUrl === '')
{
// We're running the setup of the first install...
$sRootUrl = '../';
}
$sCSSUrl = $sRootUrl.$sCssRelPath;
$this->add_linked_stylesheet($sCSSUrl);
}
/**
* Add some custom header to the page
*/
@@ -292,6 +308,15 @@ class WebPage implements Page
$this->add($this->GetDetails($aFields));
}
/**
* Whether or not the page is a PDF page
* @return boolean
*/
public function is_pdf()
{
return false;
}
/**
* Records the current state of the 'html' part of the page output
@@ -392,22 +417,55 @@ class WebPage implements Page
}
/**
* Discard unexpected output data
* Discard unexpected output data (such as PHP warnings)
* This is a MUST when the Page output is DATA (download of a document, download CSV export, download ...)
*/
public function TrashUnexpectedOutput()
{
// This protection is redundant with a protection implemented in MetaModel::IncludeModule
// which detects such issues while loading module files
// Here, the purpose is to detect and discard characters produced by the code execution (echo)
$sPreviousContent = ob_get_clean();
if (trim($sPreviousContent) != '')
$this->bTrashUnexpectedOutput = true;
}
/**
* Read the output buffer and deal with its contents:
* - trash unexpected output if the flag has been set
* - report unexpected behaviors such as the output buffering being stopped
*
* Possible improvement: I've noticed that several output buffers are stacked,
* if they are not empty, the output will be corrupted. The solution would
* consist in unstacking all of them (and concatenate the contents).
*/
protected function ob_get_clean_safe()
{
$sOutput = ob_get_contents();
if ($sOutput === false)
{
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
$sMsg = "Design/integration issue: No output buffer. Some piece of code has called ob_get_clean() or ob_end_clean() without calling ob_start()";
if ($this->bTrashUnexpectedOutput)
{
IssueLog::Error("Output already started before downloading file:\nContent was:'$sPreviousContent'\n");
IssueLog::Error($sMsg);
$sOutput = '';
}
else
{
$sOutput = $sMsg;
}
}
else
{
ob_end_clean(); // on some versions of PHP doing so when the output buffering is stopped can cause a notice
if ($this->bTrashUnexpectedOutput)
{
if (trim($sOutput) != '')
{
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
{
IssueLog::Error("Trashing unexpected output:'$sOutput'\n");
}
}
$sOutput = '';
}
}
return $sOutput;
}
/**
@@ -419,12 +477,13 @@ class WebPage implements Page
{
header($s_header);
}
$s_captured_output = ob_get_contents();
ob_end_clean();
$s_captured_output = $this->ob_get_clean_safe();
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
echo "<html>\n";
echo "<head>\n";
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\" />";
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
echo $this->get_base_tag();
foreach($this->a_linked_scripts as $s_script)
@@ -453,11 +512,19 @@ class WebPage implements Page
$this->output_dict_entries();
foreach($this->a_linked_stylesheets as $a_stylesheet)
{
if (strpos($a_stylesheet['link'], '?') === false)
{
$s_stylesheet = $a_stylesheet['link']."?itopversion=".ITOP_VERSION;
}
else
{
$s_stylesheet = $a_stylesheet['link']."&itopversion=".ITOP_VERSION;
}
if ($a_stylesheet['condition'] != "")
{
echo "<!--[if {$a_stylesheet['condition']}]>\n";
}
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$a_stylesheet['link']}\" />\n";
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
if ($a_stylesheet['condition'] != "")
{
echo "<![endif]-->\n";
@@ -475,7 +542,7 @@ class WebPage implements Page
}
if (class_exists('MetaModel') && MetaModel::GetConfig())
{
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?itopversion=".ITOP_VERSION."\" />\n";
}
echo "</head>\n";
echo "<body>\n";
@@ -488,22 +555,29 @@ class WebPage implements Page
echo "</body>\n";
echo "</html>\n";
if (class_exists('MetaModel'))
if (class_exists('DBSearch'))
{
MetaModel::RecordQueryTrace();
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}
/**
* Build a series of hidden field[s] from an array
*/
// By Rom - je verrais bien une serie d'outils pour gerer des parametres que l'on retransmet entre pages d'un wizard...
// ptet deriver webpage en webwizard
public function add_input_hidden($sLabel, $aData)
{
foreach($aData as $sKey=>$sValue)
foreach($aData as $sKey => $sValue)
{
$this->add("<input type=\"hidden\" name=\"".$sLabel."[$sKey]\" value=\"$sValue\">");
// Note: protection added to protect against the Notice 'array to string conversion' that appeared with PHP 5.4
// (this function seems unused though!)
if (is_scalar($sValue))
{
$this->add("<input type=\"hidden\" name=\"".$sLabel."[$sKey]\" value=\"$sValue\">");
}
}
}
@@ -610,7 +684,16 @@ class WebPage implements Page
}
return $bResult;
}
/**
* Check whether the output must be printable (using print.css, for sure!)
* @return bool ...
*/
public function IsPrintableVersion()
{
return $this->bPrintable;
}
/**
* Retrieves the value of a named output option for the given format
* @param string $sFormat The format: html or pdf
@@ -647,56 +730,401 @@ class WebPage implements Page
{
$sPrevUrl = '';
$sHtml = '';
foreach ($aActions as $aAction)
if (!$this->IsPrintableVersion())
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
$sOnClick = isset($aAction['onclick']) ? " onclick=\"{$aAction['onclick']}\"" : "";
if (empty($aAction['url']))
foreach ($aActions as $aAction)
{
if ($sPrevUrl != '') // Don't output consecutively two separators...
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
if (empty($aAction['url']))
{
$sHtml .= "<li>{$aAction['label']}</li>";
if ($sPrevUrl != '') // Don't output consecutively two separators...
{
$sHtml .= "<li>{$aAction['label']}</li>";
}
$sPrevUrl = '';
}
else
{
$sHtml .= "<li><a $sTarget href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
$sPrevUrl = $aAction['url'];
}
$sPrevUrl = '';
}
else
$sHtml .= "</ul></li></ul></div>";
foreach(array_reverse($aFavoriteActions) as $aAction)
{
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
$sPrevUrl = $aAction['url'];
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";
}
}
$sHtml .= "</ul></li></ul></div>";
foreach(array_reverse($aFavoriteActions) as $aAction)
{
$sHtml .= "<div class=\"actions_button\"><a href='{$aAction['url']}'>{$aAction['label']}</a></div>";
}
}
return $sHtml;
}
protected function output_dict_entries()
protected function output_dict_entries($bReturnOutput = false)
{
$sHtml = '';
if (count($this->a_dict_entries)>0)
{
echo "<script type=\"text/javascript\">\n";
echo "var Dict = {};\n";
echo "Dict._entries = {};\n";
echo "Dict.S = function(sEntry) {\n";
echo " if (sEntry in Dict._entries)\n";
echo " {\n";
echo " return Dict._entries[sEntry];\n";
echo " }\n";
echo " else\n";
echo " {\n";
echo " return sEntry;\n";
echo " }\n";
echo "};\n";
$sHtml .= "<script type=\"text/javascript\">\n";
$sHtml .= "var Dict = {};\n";
$sHtml .= "Dict._entries = {};\n";
$sHtml .= "Dict.S = function(sEntry) {\n";
$sHtml .= " if (sEntry in Dict._entries)\n";
$sHtml .= " {\n";
$sHtml .= " return Dict._entries[sEntry];\n";
$sHtml .= " }\n";
$sHtml .= " else\n";
$sHtml .= " {\n";
$sHtml .= " return sEntry;\n";
$sHtml .= " }\n";
$sHtml .= "};\n";
foreach($this->a_dict_entries as $s_entry => $s_value)
{
echo "Dict._entries['$s_entry'] = '".addslashes($s_value)."';\n";
$sHtml .= "Dict._entries['$s_entry'] = '".addslashes($s_value)."';\n";
}
echo "</script>\n";
$sHtml .= "</script>\n";
}
if ($bReturnOutput)
{
return $sHtml;
}
else
{
echo $sHtml;
}
}
}
?>
interface iTabbedPage
{
public function AddTabContainer($sTabContainer, $sPrefix = '');
public function AddToTab($sTabContainer, $sTabLabel, $sHtml);
public function SetCurrentTabContainer($sTabContainer = '');
public function SetCurrentTab($sTabLabel = '');
/**
* Add a tab which content will be loaded asynchronously via the supplied URL
*
* Limitations:
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
* Static content cannot be added inside such tabs.
*
* @param string $sTabLabel The (localised) label of the tab
* @param string $sUrl The URL to load (on the same server)
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
* @since 2.0.3
*/
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true);
public function GetCurrentTab();
public function RemoveTab($sTabLabel, $sTabContainer = null);
/**
* Finds the tab whose title matches a given pattern
* @return mixed The name of the tab as a string or false if not found
*/
public function FindTab($sPattern, $sTabContainer = null);
}
/**
* Helper class to implement JQueryUI tabs inside a page
*/
class TabManager
{
protected $m_aTabs;
protected $m_sCurrentTabContainer;
protected $m_sCurrentTab;
public function __construct()
{
$this->m_aTabs = array();
$this->m_sCurrentTabContainer = '';
$this->m_sCurrentTab = '';
}
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->m_aTabs[$sTabContainer] = array('prefix' => $sPrefix, 'tabs' => array());
return "\$Tabs:$sTabContainer\$";
}
public function AddToCurrentTab($sHtml)
{
$this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml);
}
public function GetCurrentTabLength($sHtml)
{
$iLength = isset($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html']): 0;
return $iLength;
}
/**
* Truncates the given tab to the specifed length and returns the truncated part
* @param string $sTabContainer The tab container in which to truncate the tab
* @param string $sTab The name/identifier of the tab to truncate
* @param integer $iLength The length/offset at which to truncate the tab
* @return string The truncated part
*/
public function TruncateTab($sTabContainer, $sTab, $iLength)
{
$sResult = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'], $iLength);
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'] = substr($this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$this->m_sCurrentTab]['html'], 0, $iLength);
return $sResult;
}
public function TabExists($sTabContainer, $sTab)
{
return isset($this->m_aTabs[$sTabContainer]['tabs'][$sTab]);
}
public function TabsContainerCount()
{
return count($this->m_aTabs);
}
public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
{
if (!isset($this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]))
{
// Set the content of the tab
$this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel] = array(
'type' => 'html',
'html' => $sHtml,
);
}
else
{
if ($this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]['type'] != 'html')
{
throw new Exception("Cannot add HTML content to the tab '$sTabLabel' of type '{$this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]['type']}'");
}
// Append to the content of the tab
$this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]['html'] .= $sHtml;
}
return ''; // Nothing to add to the page for now
}
public function SetCurrentTabContainer($sTabContainer = '')
{
$sPreviousTabContainer = $this->m_sCurrentTabContainer;
$this->m_sCurrentTabContainer = $sTabContainer;
return $sPreviousTabContainer;
}
public function SetCurrentTab($sTabLabel = '')
{
$sPreviousTab = $this->m_sCurrentTab;
$this->m_sCurrentTab = $sTabLabel;
return $sPreviousTab;
}
/**
* Add a tab which content will be loaded asynchronously via the supplied URL
*
* Limitations:
* Cross site scripting is not not allowed for security reasons. Use a normal tab with an IFRAME if you want to pull content from another server.
* Static content cannot be added inside such tabs.
*
* @param string $sTabLabel The (localised) label of the tab
* @param string $sUrl The URL to load (on the same server)
* @param boolean $bCache Whether or not to cache the content of the tab once it has been loaded. flase will cause the tab to be reloaded upon each activation.
* @since 2.0.3
*/
public function AddAjaxTab($sTabLabel, $sUrl, $bCache = true)
{
// Set the content of the tab
$this->m_aTabs[$this->m_sCurrentTabContainer]['tabs'][$sTabLabel] = array(
'type' => 'ajax',
'url' => $sUrl,
'cache' => $bCache,
);
return ''; // Nothing to add to the page for now
}
public function GetCurrentTabContainer()
{
return $this->m_sCurrentTabContainer;
}
public function GetCurrentTab()
{
return $this->m_sCurrentTab;
}
public function RemoveTab($sTabLabel, $sTabContainer = null)
{
if ($sTabContainer == null)
{
$sTabContainer = $this->m_sCurrentTabContainer;
}
if (isset($this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]))
{
// Delete the content of the tab
unset($this->m_aTabs[$sTabContainer]['tabs'][$sTabLabel]);
// If we just removed the active tab, let's reset the active tab
if (($this->m_sCurrentTabContainer == $sTabContainer) && ($this->m_sCurrentTab == $sTabLabel))
{
$this->m_sCurrentTab = '';
}
}
}
/**
* Finds the tab whose title matches a given pattern
* @return mixed The actual name of the tab (as a string) or false if not found
*/
public function FindTab($sPattern, $sTabContainer = null)
{
$result = false;
if ($sTabContainer == null)
{
$sTabContainer = $this->m_sCurrentTabContainer;
}
foreach($this->m_aTabs[$sTabContainer]['tabs'] as $sTabLabel => $void)
{
if (preg_match($sPattern, $sTabLabel))
{
$result = $sTabLabel;
break;
}
}
return $result;
}
/**
* Make the given tab the active one, as if it were clicked
* DOES NOT WORK: apparently in the *old* version of jquery
* that we are using this is not supported... TO DO upgrade
* the whole jquery bundle...
*/
public function SelectTab($sTabContainer, $sTabLabel)
{
$container_index = 0;
$tab_index = 0;
foreach($this->m_aTabs as $sCurrentTabContainerName => $aTabs)
{
if ($sTabContainer == $sCurrentTabContainerName)
{
foreach($aTabs['tabs'] as $sCurrentTabLabel => $void)
{
if ($sCurrentTabLabel == $sTabLabel)
{
break;
}
$tab_index++;
}
break;
}
$container_index++;
}
$sSelector = '#tabbedContent_'.$container_index.' > ul';
return "window.setTimeout(\"$('$sSelector').tabs('select', $tab_index);\", 100);"; // Let the time to the tabs widget to initialize
}
public function RenderIntoContent($sContent, WebPage $oPage)
{
// Render the tabs in the page (if any)
foreach($this->m_aTabs as $sTabContainerName => $aTabs)
{
$sTabs = '';
$sPrefix = $aTabs['prefix'];
$container_index = 0;
if (count($aTabs['tabs']) > 0)
{
if ($oPage->IsPrintableVersion())
{
$oPage->add_ready_script(
<<< EOF
oHiddeableChapters = {};
EOF
);
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
$i = 0;
foreach($aTabs['tabs'] as $sTabName => $aTabData)
{
$sTabNameEsc = addslashes($sTabName);
$sTabId = "tab_{$sPrefix}{$container_index}$i";
switch($aTabData['type'])
{
case 'ajax':
$sTabHtml = '';
$sUrl = $aTabData['url'];
$oPage->add_ready_script(
<<< EOF
$.post('$sUrl', {printable: '1'}, function(data){
$('#$sTabId > .printable-tab-content').append(data);
});
EOF
);
break;
case 'html':
default:
$sTabHtml = $aTabData['html'];
}
$sTabs .= "<div class=\"printable-tab\" id=\"$sTabId\"><h2 class=\"printable-tab-title\">".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</h2><div class=\"printable-tab-content\">".$sTabHtml."</div></div>\n";
$oPage->add_ready_script(
<<< EOF
oHiddeableChapters['$sTabId'] = '$sTabNameEsc';
EOF
);
$i++;
}
$sTabs .= "</div>\n<!-- end of tabs-->\n";
}
else
{
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
$sTabs .= "<ul>\n";
// Display the unordered list that will be rendered as the tabs
$i = 0;
foreach($aTabs['tabs'] as $sTabName => $aTabData)
{
switch($aTabData['type'])
{
case 'ajax':
$sTabs .= "<li data-cache=\"".($aTabData['cache'] ? 'true' : 'false')."\"><a href=\"{$aTabData['url']}\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
break;
case 'html':
default:
$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
}
$i++;
}
$sTabs .= "</ul>\n";
// Now add the content of the tabs themselves
$i = 0;
foreach($aTabs['tabs'] as $sTabName => $aTabData)
{
switch($aTabData['type'])
{
case 'ajax':
// Nothing to add
break;
case 'html':
default:
$sTabs .= "<div id=\"tab_{$sPrefix}{$container_index}$i\">".$aTabData['html']."</div>\n";
}
$i++;
}
$sTabs .= "</div>\n<!-- end of tabs-->\n";
}
}
$sContent = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $sContent);
$container_index++;
}
return $sContent;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class WizardHelper
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -35,7 +35,7 @@ class WizardHelper
}
/**
* Constructs the PHP target object from the parameters sent to the web page by the wizard
* @param boolean $bReadUploadedFiles True to also ready any uploaded file (for blob/document fields)
* @param boolean $bReadUploadedFiles True to also read any uploaded file (for blob/document fields)
* @return object
*/
public function GetTargetObject($bReadUploadedFiles = false)
@@ -52,7 +52,7 @@ class WizardHelper
{
// Because this is stored in a Javascript array, unused indexes
// are filled with null values and unused keys (stored as strings) contain $$NULL$$
if ( ($sAttCode !='id') && ($sAttCode !== false) && ($value !== null) && ($value !== '$$NULL$$'))
if ( ($sAttCode !='id') && ($value !== '$$NULL$$'))
{
$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
if (($oAttDef->IsLinkSet()) && ($value != '') )
@@ -109,12 +109,34 @@ class WizardHelper
$oObj->Set($sAttCode, $oDocument);
}
}
else if ( $oAttDef->GetEditClass() == 'Image' )
{
if ($bReadUploadedFiles)
{
$oDocument = utils::ReadPostedDocument('attr_'.$sAttCode, 'fcontents');
$oObj->Set($sAttCode, $oDocument);
}
else
{
// Create a new empty document, just for displaying the file name
$oDocument = new ormDocument(null, '', $value);
$oObj->Set($sAttCode, $oDocument);
}
}
else if (($oAttDef->IsExternalKey()) && (!empty($value)) && ($value > 0) )
{
// For external keys: load the target object so that external fields
// get filled too
$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value);
$oObj->Set($sAttCode, $oTargetObj);
$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value, false);
if ($oTargetObj)
{
$oObj->Set($sAttCode, $oTargetObj);
}
else
{
// May happen for security reasons (portal, see ticket #1074)
$oObj->Set($sAttCode, $value);
}
}
else
{
@@ -276,4 +298,3 @@ class WizardHelper
return $oSet;
}
}
?>

View File

@@ -0,0 +1,467 @@
<?php
/* @author Mark Jones
* @license MIT License
* */
if (!class_exists('ZipArchive')) { throw new Exception('ZipArchive not found'); }
Class XLSXWriter
{
//------------------------------------------------------------------
protected $author ='Doc Author';
protected $sheets_meta = array();
protected $shared_strings = array();//unique set
protected $shared_string_count = 0;//count of non-unique references to the unique set
protected $temp_files = array();
protected $date_format = 'YYYY-MM-DD';
protected $date_time_format = 'YYYY-MM-DD\ HH:MM:SS';
public function __construct(){}
public function setAuthor($author='') { $this->author=$author; }
public function __destruct()
{
if (!empty($this->temp_files)) {
foreach($this->temp_files as $temp_file) {
@unlink($temp_file);
}
}
}
public function setDateFormat($date_format)
{
$this->date_format = $date_format;
}
public function setDateTimeFormat($date_time_format)
{
$this->date_time_format = $date_time_format;
}
protected function tempFilename()
{
$filename = tempnam("/tmp", "xlsx_writer_");
$this->temp_files[] = $filename;
return $filename;
}
public function writeToStdOut()
{
$temp_file = $this->tempFilename();
self::writeToFile($temp_file);
readfile($temp_file);
}
public function writeToString()
{
$temp_file = $this->tempFilename();
self::writeToFile($temp_file);
$string = file_get_contents($temp_file);
return $string;
}
public function writeToFile($filename)
{
@unlink($filename);//if the zip already exists, overwrite it
$zip = new ZipArchive();
if (empty($this->sheets_meta)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined."); return; }
if (!$zip->open($filename, ZipArchive::CREATE)) { self::log("Error in ".__CLASS__."::".__FUNCTION__.", unable to create zip."); return; }
$zip->addEmptyDir("docProps/");
$zip->addFromString("docProps/app.xml" , self::buildAppXML() );
$zip->addFromString("docProps/core.xml", self::buildCoreXML());
$zip->addEmptyDir("_rels/");
$zip->addFromString("_rels/.rels", self::buildRelationshipsXML());
$zip->addEmptyDir("xl/worksheets/");
foreach($this->sheets_meta as $sheet_meta) {
$zip->addFile($sheet_meta['filename'], "xl/worksheets/".$sheet_meta['xmlname'] );
}
if (!empty($this->shared_strings)) {
$zip->addFile($this->writeSharedStringsXML(), "xl/sharedStrings.xml" ); //$zip->addFromString("xl/sharedStrings.xml", self::buildSharedStringsXML() );
}
$zip->addFromString("xl/workbook.xml" , self::buildWorkbookXML() );
$zip->addFile($this->writeStylesXML(), "xl/styles.xml" ); //$zip->addFromString("xl/styles.xml" , self::buildStylesXML() );
$zip->addFromString("[Content_Types].xml" , self::buildContentTypesXML() );
$zip->addEmptyDir("xl/_rels/");
$zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML() );
$zip->close();
}
public function writeSheet(array $data, $sheet_name='', array $header_types=array(), array $header_row=array() )
{
$data = empty($data) ? array( array('') ) : $data;
$sheet_filename = $this->tempFilename();
$sheet_default = 'Sheet'.(count($this->sheets_meta)+1);
$sheet_name = !empty($sheet_name) ? $sheet_name : $sheet_default;
$this->sheets_meta[] = array('filename'=>$sheet_filename, 'sheetname'=>$sheet_name ,'xmlname'=>strtolower($sheet_default).".xml" );
$header_offset = empty($header_types) ? 0 : 1;
$row_count = count($data) + $header_offset;
$column_count = count($data[self::array_first_key($data)]);
$max_cell = self::xlsCell( $row_count-1, $column_count-1 );
$tabselected = count($this->sheets_meta)==1 ? 'true' : 'false';//only first sheet is selected
$cell_formats_arr = empty($header_types) ? array_fill(0, $column_count, 'string') : array_values($header_types);
if (empty($header_row) && !empty($header_types))
{
$header_row = empty($header_types) ? array() : array_keys($header_types);
}
$fd = fopen($sheet_filename, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd,'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
fwrite($fd, '<sheetPr filterMode="false">');
fwrite($fd, '<pageSetUpPr fitToPage="false"/>');
fwrite($fd, '</sheetPr>');
fwrite($fd, '<dimension ref="A1:'.$max_cell.'"/>');
fwrite($fd, '<sheetViews>');
fwrite($fd, '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" tabSelected="'.$tabselected.'" topLeftCell="A1" view="normal" windowProtection="false" workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">');
fwrite($fd, '<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
fwrite($fd, '</sheetView>');
fwrite($fd, '</sheetViews>');
fwrite($fd, '<cols>');
fwrite($fd, '<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="19"/>');
fwrite($fd, '</cols>');
fwrite($fd, '<sheetData>');
if (!empty($header_row))
{
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">');
foreach($header_row as $k=>$v)
{
$this->writeCell($fd, 0, $k, $v, $cell_format='string');
}
fwrite($fd, '</row>');
}
foreach($data as $i=>$row)
{
fwrite($fd, '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.($i+$header_offset+1).'">');
foreach($row as $k=>$v)
{
$this->writeCell($fd, $i+$header_offset, $k, $v, $cell_formats_arr[$k]);
}
fwrite($fd, '</row>');
}
fwrite($fd, '</sheetData>');
fwrite($fd, '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>');
fwrite($fd, '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>');
fwrite($fd, '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>');
fwrite($fd, '<headerFooter differentFirst="false" differentOddEven="false">');
fwrite($fd, '<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>');
fwrite($fd, '<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>');
fwrite($fd, '</headerFooter>');
fwrite($fd,'</worksheet>');
fclose($fd);
}
protected function writeCell($fd, $row_number, $column_number, $value, $cell_format)
{
static $styles = array('money'=>1,'dollar'=>1,'datetime'=>2,'date'=>3,'string'=>0);
$cell = self::xlsCell($row_number, $column_number);
$s = isset($styles[$cell_format]) && ($value !== '') ? $styles[$cell_format] : '0';
if (is_int($value) || is_float($value)) {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.($value*1).'</v></c>');//int,float, etc
} else if (($cell_format=='date') && ($value != '')) {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.intval(self::convert_date_time($value)).'</v></c>');
} else if (($cell_format=='datetime') && ($value != '')) {
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
} else if ($value==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
} else if ($value{0}=='='){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><f>'.self::xmlspecialchars($value).'</f></c>');
} else if ($value!==''){
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="s"><v>'.self::xmlspecialchars($this->setSharedString($value)).'</v></c>');
}
}
protected function writeStylesXML()
{
$tempfile = $this->tempFilename();
$fd = fopen($tempfile, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd, '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
fwrite($fd, '<numFmts count="4">');
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
fwrite($fd, '<numFmt formatCode="'.$this->date_time_format.'" numFmtId="166"/>');
fwrite($fd, '<numFmt formatCode="'.$this->date_format.'" numFmtId="167"/>');
fwrite($fd, '</numFmts>');
fwrite($fd, '<fonts count="4">');
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
fwrite($fd, '</fonts>');
fwrite($fd, '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
fwrite($fd, '<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
fwrite($fd, '<cellStyleXfs count="15">');
fwrite($fd, '<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
fwrite($fd, '<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
fwrite($fd, '<protection hidden="false" locked="true"/>');
fwrite($fd, '</xf>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
fwrite($fd, '</cellStyleXfs>');
fwrite($fd, '<cellXfs count="4">');
fwrite($fd, '<xf applyAlignment="1" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"><alignment wrapText="1"/></xf>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="165" xfId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="166" xfId="0"/>');
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="167" xfId="0"/>');
fwrite($fd, '</cellXfs>');
fwrite($fd, '<cellStyles count="1">');
fwrite($fd, '<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
//fwrite($fd, '<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
//fwrite($fd, '<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
//fwrite($fd, '<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
//fwrite($fd, '<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
//fwrite($fd, '<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
fwrite($fd, '</cellStyles>');
fwrite($fd, '</styleSheet>');
fclose($fd);
return $tempfile;
}
protected function setSharedString($v)
{
// Strip control characters which Excel does not seem to like...
$v = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F]/u', '', $v);
if (isset($this->shared_strings[$v]))
{
$string_value = $this->shared_strings[$v];
}
else
{
$string_value = count($this->shared_strings);
$this->shared_strings[$v] = $string_value;
}
$this->shared_string_count++;//non-unique count
return $string_value;
}
protected function writeSharedStringsXML()
{
$tempfile = $this->tempFilename();
$fd = fopen($tempfile, "w+");
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
fwrite($fd,'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
fwrite($fd,'<sst count="'.($this->shared_string_count).'" uniqueCount="'.count($this->shared_strings).'" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
foreach($this->shared_strings as $s=>$c)
{
fwrite($fd,'<si><t>'.self::xmlspecialchars($s).'</t></si>');
}
fwrite($fd, '</sst>');
fclose($fd);
return $tempfile;
}
protected function buildAppXML()
{
$app_xml="";
$app_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$app_xml.='<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><TotalTime>0</TotalTime></Properties>';
return $app_xml;
}
protected function buildCoreXML()
{
$core_xml="";
$core_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$core_xml.='<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
$core_xml.='<dcterms:created xsi:type="dcterms:W3CDTF">'.date("Y-m-d\TH:i:s.00\Z").'</dcterms:created>';//$date_time = '2013-07-25T15:54:37.00Z';
$core_xml.='<dc:creator>'.self::xmlspecialchars($this->author).'</dc:creator>';
$core_xml.='<cp:revision>0</cp:revision>';
$core_xml.='</cp:coreProperties>';
return $core_xml;
}
protected function buildRelationshipsXML()
{
$rels_xml="";
$rels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$rels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
$rels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>';
$rels_xml.='<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>';
$rels_xml.='<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>';
$rels_xml.="\n";
$rels_xml.='</Relationships>';
return $rels_xml;
}
protected function buildWorkbookXML()
{
$workbook_xml="";
$workbook_xml.='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n";
$workbook_xml.='<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">';
$workbook_xml.='<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>';
$workbook_xml.='<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true" showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>';
$workbook_xml.='<sheets>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$workbook_xml.='<sheet name="'.self::xmlspecialchars($sheet_meta['sheetname']).'" sheetId="'.($i+1).'" state="visible" r:id="rId'.($i+2).'"/>';
}
$workbook_xml.='</sheets>';
$workbook_xml.='<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/></workbook>';
return $workbook_xml;
}
protected function buildWorkbookRelsXML()
{
$wkbkrels_xml="";
$wkbkrels_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$wkbkrels_xml.='<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
$wkbkrels_xml.='<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$wkbkrels_xml.='<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/'.($sheet_meta['xmlname']).'"/>';
}
if (!empty($this->shared_strings)) {
$wkbkrels_xml.='<Relationship Id="rId'.(count($this->sheets_meta)+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
}
$wkbkrels_xml.="\n";
$wkbkrels_xml.='</Relationships>';
return $wkbkrels_xml;
}
protected function buildContentTypesXML()
{
$content_types_xml="";
$content_types_xml.='<?xml version="1.0" encoding="UTF-8"?>'."\n";
$content_types_xml.='<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
$content_types_xml.='<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
$content_types_xml.='<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
foreach($this->sheets_meta as $i=>$sheet_meta) {
$content_types_xml.='<Override PartName="/xl/worksheets/'.($sheet_meta['xmlname']).'" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
}
if (!empty($this->shared_strings)) {
$content_types_xml.='<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>';
}
$content_types_xml.='<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>';
$content_types_xml.='<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>';
$content_types_xml.='<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>';
$content_types_xml.='<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>';
$content_types_xml.="\n";
$content_types_xml.='</Types>';
return $content_types_xml;
}
//------------------------------------------------------------------
/*
* @param $row_number int, zero based
* @param $column_number int, zero based
* @return Cell label/coordinates, ex: A1, C3, AA42
* */
public static function xlsCell($row_number, $column_number)
{
$n = $column_number;
for($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
$r = chr($n%26 + 0x41) . $r;
}
return $r . ($row_number+1);
}
//------------------------------------------------------------------
public static function log($string)
{
file_put_contents("php://stderr", date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n");
}
//------------------------------------------------------------------
public static function xmlspecialchars($val)
{
return str_replace("'", "&#39;", htmlspecialchars($val));
}
//------------------------------------------------------------------
public static function array_first_key(array $arr)
{
reset($arr);
$first_key = key($arr);
return $first_key;
}
//------------------------------------------------------------------
public static function convert_date_time($date_input) //thanks to Excel::Writer::XLSX::Worksheet.pm (perl)
{
$days = 0; # Number of days since epoch
$seconds = 0; # Time expressed as fraction of 24h hours in seconds
$year=$month=$day=0;
$hour=$min =$sec=0;
$date_time = $date_input;
if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $date_time, $matches))
{
list($junk,$year,$month,$day) = $matches;
}
if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $date_time, $matches))
{
list($junk,$hour,$min,$sec) = $matches;
$seconds = ( $hour * 60 * 60 + $min * 60 + $sec ) / ( 24 * 60 * 60 );
}
//using 1900 as epoch, not 1904, ignoring 1904 special case
# Special cases for Excel.
if ("$year-$month-$day"=='1899-12-31') return $seconds ; # Excel 1900 epoch
if ("$year-$month-$day"=='1900-01-00') return $seconds ; # Excel 1900 epoch
if ("$year-$month-$day"=='1900-02-29') return 60 + $seconds ; # Excel false leapday
# We calculate the date by calculating the number of days since the epoch
# and adjust for the number of leap days. We calculate the number of leap
# days by normalising the year in relation to the epoch. Thus the year 2000
# becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
$epoch = 1900;
$offset = 0;
$norm = 300;
$range = $year - $epoch;
# Set month days and check for leap year.
$leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
$mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
# Some boundary checks
if($year < $epoch || $year > 9999) return 0;
if($month < 1 || $month > 12) return 0;
if($day < 1 || $day > $mdays[ $month - 1 ]) return 0;
# Accumulate the number of days since the epoch.
$days = $day; # Add days for current month
$days += array_sum( array_slice($mdays, 0, $month-1 ) ); # Add days for past months
$days += $range * 365; # Add days for past years
$days += intval( ( $range ) / 4 ); # Add leapdays
$days -= intval( ( $range + $offset ) / 100 ); # Subtract 100 year leapdays
$days += intval( ( $range + $offset + $norm ) / 400 ); # Add 400 year leapdays
$days -= $leap; # Already counted above
# Adjust for Excel erroneously treating 1900 as a leap year.
if ($days > 59) { $days++;}
return $days + $seconds;
}
//------------------------------------------------------------------
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class XMLPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -51,6 +51,9 @@ class XMLPage extends WebPage
{
if (!$this->m_bPassThrough)
{
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
$this->s_content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?".">\n".trim($this->s_content);
$this->add_header("Content-Length: ".strlen($this->s_content));
foreach($this->a_headers as $s_header)
@@ -59,9 +62,9 @@ class XMLPage extends WebPage
}
echo $this->s_content;
}
if (class_exists('MetaModel'))
if (class_exists('DBSearch'))
{
MetaModel::RecordQueryTrace();
DBSearch::RecordQueryTrace();
}
}
@@ -79,8 +82,7 @@ class XMLPage extends WebPage
}
else
{
$s_captured_output = ob_get_contents();
ob_end_clean();
$s_captured_output = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
@@ -101,13 +103,4 @@ class XMLPage extends WebPage
public function table($aConfig, $aData, $aParams = array())
{
}
public function TrashUnexpectedOutput()
{
if (!$this->m_bPassThrough)
{
parent::TrashUnexpectedOutput();
}
}
}
?>

View File

@@ -219,7 +219,7 @@ class MyHelpers
}
}
public static function get_callstack_html($iLevelsToIgnore = 0, $aCallStack = null)
public static function get_callstack($iLevelsToIgnore = 0, $aCallStack = null)
{
if ($aCallStack == null) $aCallStack = debug_backtrace();
@@ -231,6 +231,16 @@ class MyHelpers
{
$sLine = empty($aCallInfo['line']) ? "" : $aCallInfo['line'];
$sFile = empty($aCallInfo['file']) ? "" : $aCallInfo['file'];
if ($sFile != '')
{
$sFile = str_replace('\\', '/', $sFile);
$sAppRoot = str_replace('\\', '/', APPROOT);
$iPos = strpos($sFile, $sAppRoot);
if ($iPos !== false)
{
$sFile = substr($sFile, strlen($sAppRoot));
}
}
$sClass = empty($aCallInfo['class']) ? "" : $aCallInfo['class'];
$sType = empty($aCallInfo['type']) ? "" : $aCallInfo['type'];
$sFunction = empty($aCallInfo['function']) ? "" : $aCallInfo['function'];
@@ -259,11 +269,11 @@ class MyHelpers
$args .= $a;
break;
case 'string':
$a = Str::pure2html(self::beautifulstr($a, 1024, true, true));
$a = Str::pure2html(self::beautifulstr($a, 64, true, false));
$args .= "\"$a\"";
break;
case 'array':
$args .= 'Array('.count($a).')';
$args .= 'array('.count($a).')';
break;
case 'object':
$args .= 'Object('.get_class($a).')';
@@ -272,19 +282,25 @@ class MyHelpers
$args .= 'Resource('.strstr($a, '#').')';
break;
case 'boolean':
$args .= $a ? 'True' : 'False';
$args .= $a ? 'true' : 'false';
break;
case 'NULL':
$args .= 'Null';
$args .= 'null';
break;
default:
$args .= 'Unknown';
}
}
$sFunctionInfo = "$sClass $sType $sFunction($args)";
$sFunctionInfo = "$sClass$sType$sFunction($args)";
}
$aDigestCallStack[] = array('File'=>$sFile, 'Line'=>$sLine, 'Function'=>$sFunctionInfo);
}
return $aDigestCallStack;
}
public static function get_callstack_html($iLevelsToIgnore = 0, $aCallStack = null)
{
$aDigestCallStack = self::get_callstack($iLevelsToIgnore, $aCallStack);
return self::make_table_from_assoc_array($aDigestCallStack);
}
@@ -293,6 +309,17 @@ class MyHelpers
return self::get_callstack_html($iLevelsToIgnore, $aCallStack);
}
public static function get_callstack_text($iLevelsToIgnore = 0, $aCallStack = null)
{
$aDigestCallStack = self::get_callstack($iLevelsToIgnore, $aCallStack);
$aRes = array();
foreach ($aDigestCallStack as $aCall)
{
$aRes[] = $aCall['File'].' at '.$aCall['Line'].', '.$aCall['Function'];
}
return implode("\n", $aRes);
}
///////////////////////////////////////////////////////////////////////////////
// Source: New
// Last modif: 2004/12/20 RQU

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Persistent classes (internal): user defined actions
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -136,7 +136,7 @@ class ActionEmail extends ActionNotification
{
$aParams = array
(
"category" => "core/cmdb,bizmodel",
"category" => "core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
@@ -157,7 +157,7 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
@@ -262,8 +262,10 @@ class ActionEmail extends ActionNotification
{
$sPrefix = '';
}
$oLog->Set('message', $sPrefix.$sRes);
if ($oLog)
{
$oLog->Set('message', $sPrefix . $sRes);
}
}
catch (Exception $e)
{
@@ -322,6 +324,8 @@ class ActionEmail extends ActionNotification
if (isset($sSubject)) $oLog->Set('subject', $sSubject);
if (isset($sBody)) $oLog->Set('body', $sBody);
}
$sStyles = file_get_contents(APPROOT.'css/email.css');
$sStyles .= MetaModel::GetConfig()->Get('email_css');
$oEmail = new EMail();
@@ -342,7 +346,7 @@ class ActionEmail extends ActionNotification
$sTestBody .= "</ul>\n";
$sTestBody .= "</p>\n";
$sTestBody .= "</div>\n";
$oEmail->SetBody($sTestBody);
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
$oEmail->SetReferences($sReference);
@@ -351,7 +355,7 @@ class ActionEmail extends ActionNotification
else
{
$oEmail->SetSubject($sSubject);
$oEmail->SetBody($sBody);
$oEmail->SetBody($sBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($sTo);
$oEmail->SetRecipientCC($sCC);
$oEmail->SetRecipientBCC($sBCC);

View File

@@ -34,27 +34,27 @@ class ExecAsyncTask implements iBackgroundProcess
public function Process($iTimeLimit)
{
$sOQL = "SELECT AsyncTask WHERE ISNULL(started) AND (ISNULL(planned) OR (planned < NOW()))";
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array());
$sNow = date(AttributeDateTime::GetSQLFormat());
// Criteria: planned, and expected to occur... ASAP or in the past
$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
$iProcessed = 0;
while ((time() < $iTimeLimit) && ($oTask = $oSet->Fetch()))
while (time() < $iTimeLimit)
{
$oTask->Set('started', time());
$oTask->DBUpdate();
$oTask->Process();
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
$oTask = $oSet->Fetch();
if (is_null($oTask))
{
// Nothing to be done
break;
}
$iProcessed++;
$oTask->DBDelete();
}
if ($iProcessed == $oSet->Count())
{
return "processed $iProcessed tasks";
}
else
{
return "processed $iProcessed tasks (remaining: ".($oSet->Count() - $iProcessed).")";
if ($oTask->Process())
{
$oTask->DBDelete();
}
}
return "processed $iProcessed tasks";
}
}
@@ -80,20 +80,101 @@ abstract class AsyncTask extends DBObject
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
// MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Null is allowed to ease the migration from iTop 2.0.2 and earlier, when the status did not exist, and because the default value is not taken into account in the SQL definition
// The value is set from null to planned in the setup program
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('planned,running,idle,error'), "sql"=>"status", "default_value"=>"planned", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("started", array("allowed_values"=>null, "sql"=>"started", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
// planned... still not used - reserved for timer management
MetaModel::Init_AddAttribute(new AttributeDateTime("planned", array("allowed_values"=>null, "sql"=>"planned", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("event_id", array("targetclass"=>"Event", "jointype"=> "", "allowed_values"=>null, "sql"=>"event_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
// Display lists
// MetaModel::Init_SetZListItems('details', array()); // Attributes to be displayed for the complete details
// MetaModel::Init_SetZListItems('list', array()); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
MetaModel::Init_AddAttribute(new AttributeInteger("remaining_retries", array("allowed_values"=>null, "sql"=>"remaining_retries", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("last_error_code", array("allowed_values"=>null, "sql"=>"last_error_code", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("last_error", array("allowed_values"=>null, "sql"=>"last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("last_attempt", array("allowed_values"=>null, "sql"=>"last_attempt", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
* Every is fine
*/
const OK = 0;
/**
* The task no longer exists
*/
const DELETED = 1;
/**
* The task is already being executed
*/
const ALREADY_RUNNING = 2;
/**
* The current process requests the ownership on the task.
* In case the task can be accessed concurrently, this function can be overloaded to add a critical section.
* The function must not block the caller if another process is already owning the task
*
* @return integer A code among OK/DELETED/ALREADY_RUNNING.
*/
public function MarkAsRunning()
{
try
{
if ($this->Get('status') == 'running')
{
return self::ALREADY_RUNNING;
}
else
{
$this->Set('status', 'running');
$this->Set('started', time());
$this->DBUpdate();
return self::OK;
}
}
catch(Exception $e)
{
// Corrupted task !! (for example: "Failed to reload object")
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
$oEventLog->DBUpdate();
}
$this->DBDelete();
return self::DELETED;
}
}
public function GetRetryDelay($iErrorCode = null)
{
$iRetryDelay = 600;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$iRetryDelay = $aConfig['retry_delay'];
}
return $iRetryDelay;
}
public function GetMaxRetries($iErrorCode = null)
{
$iMaxRetries = 0;
$aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$iMaxRetries = $aConfig['max_retries'];
}
}
/**
* Override to notify people that a task cannot be performed
*/
protected function OnDefinitiveFailure()
{
}
protected function OnInsert()
@@ -101,18 +182,92 @@ abstract class AsyncTask extends DBObject
$this->Set('created', time());
}
public function Process()
/**
* @return boolean True if the task record can be deleted
*/
public function Process()
{
$sStatus = $this->DoProcess();
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', $sStatus);
$oEventLog->DBUpdate();
// By default: consider that the task is not completed
$bRet = false;
// Attempt to take the ownership
$iStatus = $this->MarkAsRunning();
if ($iStatus == self::OK)
{
try
{
$sStatus = $this->DoProcess();
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', $sStatus);
$oEventLog->DBUpdate();
}
$bRet = true;
}
catch(Exception $e)
{
$this->HandleError($e->getMessage(), $e->getCode());
}
}
else
{
// Already done or being handled by another process... skip...
$bRet = false;
}
return $bRet;
}
/**
* Overridable to extend the behavior in case of error (logging)
*/
protected function HandleError($sErrorMessage, $iErrorCode)
{
if ($this->Get('last_attempt') == '')
{
// First attempt
$this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode));
}
$this->Set('last_error', $sErrorMessage);
$this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!!
$this->Set('last_attempt', time());
$iRemaining = $this->Get('remaining_retries');
if ($iRemaining > 0)
{
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
$this->Set('remaining_retries', $iRemaining - 1);
$this->Set('status', 'planned');
$this->Set('started', null);
$this->Set('planned', time() + $iRetryDelay);
}
else
{
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage);
$this->Set('status', 'error');
$this->Set('started', null);
$this->Set('planned', null);
$this->OnDefinitiveFailure();
}
$this->DBUpdate();
}
/**
* Throws an exception (message and code)
*/
abstract public function DoProcess();
/**
* Describes the error codes that DoProcess can return by the mean of exceptions
*/
static public function EnumErrorCodes()
{
return array();
}
}
/**
@@ -140,7 +295,7 @@ class AsyncSendEmail extends AsyncTask
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeInteger("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>Email::ORIGINAL_FORMAT, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));

File diff suppressed because it is too large Load Diff

37
core/autoload.php Normal file
View File

@@ -0,0 +1,37 @@
<?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/>
MetaModel::IncludeModule('application/transaction.class.inc.php');
MetaModel::IncludeModule('application/menunode.class.inc.php');
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('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');
MetaModel::IncludeModule('core/trigger.class.inc.php');
MetaModel::IncludeModule('core/bulkexport.class.inc.php');
MetaModel::IncludeModule('core/ownershiplock.class.inc.php');
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
MetaModel::IncludeModule('webservices/webservices.basic.php');
//MetaModel::IncludeModule('addons', 'user rights', 'addons/userrights/userrightsprofile.class.inc.php');

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,17 +18,50 @@
/**
* Class BackgroundProcess
* interface iProcess
* Something that can be executed
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface iProcess
{
public function Process($iUnixTimeLimit);
}
/**
* interface iBackgroundProcess
* Any extension that must be called regularly to be executed in the background
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface iBackgroundProcess
interface iBackgroundProcess extends iProcess
{
/*
Gives the repetition rate in seconds
@returns integer
*/
public function GetPeriodicity();
public function Process($iUnixTimeLimit);
}
/**
* interface iScheduledProcess
* A variant of process that must be called at specific times
*
* @copyright Copyright (C) 2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface iScheduledProcess extends iProcess
{
/*
Gives the exact time at which the process must be run next time
@returns DateTime
*/
public function GetNextOccurrence();
}
?>

View File

@@ -0,0 +1,76 @@
<?php
// Copyright (C) 2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class BackgroundTask
* A class to record information about the execution of background processes
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class BackgroundTask extends DBObject
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "class_name",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_backgroundtask",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("class_name", array("allowed_values"=>null, "sql"=>"class_name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("first_run_date", array("allowed_values"=>null, "sql"=>"first_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("latest_run_date", array("allowed_values"=>null, "sql"=>"latest_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("next_run_date", array("allowed_values"=>null, "sql"=>"next_run_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("total_exec_count", array("allowed_values"=>null, "sql"=>"total_exec_count", "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDecimal("latest_run_duration", array("allowed_values"=>null, "sql"=>"latest_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDecimal("min_run_duration", array("allowed_values"=>null, "sql"=>"min_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDecimal("max_run_duration", array("allowed_values"=>null, "sql"=>"max_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDecimal("average_run_duration", array("allowed_values"=>null, "sql"=>"average_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBoolean("running", array("allowed_values"=>null, "sql"=>"running", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,paused'), "sql"=>"status", "default_value"=>'active', "is_null_allowed"=>false, "depends_on"=>array())));
}
public function ComputeDurations($fLatestDuration)
{
$iTotalRun = $this->Get('total_exec_count');
$fAverageDuration = ($this->Get('average_run_duration') * $iTotalRun + $fLatestDuration) / (1+$iTotalRun);
$this->Set('average_run_duration', sprintf('%.3f',$fAverageDuration));
$this->Set('total_exec_count', 1+$iTotalRun);
if ($fLatestDuration < $this->Get('min_run_duration'))
{
$this->Set('min_run_duration', sprintf('%.3f',$fLatestDuration));
}
if ($fLatestDuration > $this->Get('max_run_duration'))
{
$this->Set('max_run_duration', sprintf('%.3f',$fLatestDuration));
}
$this->Set('latest_run_duration', sprintf('%.3f',$fLatestDuration));
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Bulk change facility (common to interactive and batch usages)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -258,8 +258,9 @@ class BulkChange
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
protected $m_sDateFormat; // Date format specification, see utils::StringToTime()
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null, $bLocalize = false)
{
@@ -272,6 +273,7 @@ class BulkChange
$this->m_aOnDisappear = $aOnDisappear;
$this->m_sDateFormat = $sDateFormat;
$this->m_bLocalizedValues = $bLocalize;
$this->m_aExtKeysMappingCache = array();
}
protected $m_bReportHtml = false;
@@ -293,12 +295,19 @@ class BulkChange
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
{
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
{
// The foreign attribute is one of our reconciliation key
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
if ($sForeignAttCode == 'id')
{
$value = (int) $aRowData[$iCol];
}
else
{
// The foreign attribute is one of our reconciliation key
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
@@ -357,7 +366,8 @@ class BulkChange
}
else
{
$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
$aCacheKeys = array();
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
{
// The foreign attribute is one of our reconciliation key
@@ -370,24 +380,60 @@ class BulkChange
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
$aCacheKeys[] = $value;
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
$oExtObjects = new CMDBObjectSet($oReconFilter);
switch($oExtObjects->Count())
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
$iCount = 0;
$iForeignKey = null;
$sOQL = '';
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
if (!array_key_exists($sAttCode, $this->m_aExtKeysMappingCache))
{
case 0:
$this->m_aExtKeysMappingCache[$sAttCode] = array();
}
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
{
// Cache hit
$iCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
$sOQL = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['oql'];
// Record the hit
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
}
else
{
// Cache miss, let's initialize it
$oExtObjects = new CMDBObjectSet($oReconFilter);
$iCount = $oExtObjects->Count();
if ($iCount == 1)
{
$oForeignObj = $oExtObjects->Fetch();
$iForeignKey = $oForeignObj->GetKey();
}
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
'c' => $iCount,
'k' => $iForeignKey,
'oql' => $oReconFilter->ToOql(),
'h' => 0, // number of hits on this cache entry
);
}
switch($iCount)
{
case 0:
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
$aResults[$sAttCode]= new CellStatus_SearchIssue();
break;
case 1:
case 1:
// Do change the external key attribute
$oForeignObj = $oExtObjects->Fetch();
$oTargetObj->Set($sAttCode, $oForeignObj->GetKey());
$oTargetObj->Set($sAttCode, $iForeignKey);
break;
default:
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $oExtObjects->Count());
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $oExtObjects->Count(), $oReconFilter->ToOql());
default:
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
}
}
@@ -508,7 +554,15 @@ class BulkChange
else
{
// By default... nothing happens
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef instanceof AttributeDateTime)
{
$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
}
else
{
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
}
}
}
}
@@ -749,26 +803,55 @@ class BulkChange
if (!is_null($this->m_sDateFormat) && (strlen($this->m_sDateFormat) > 0))
{
$sDateTimeFormat = $this->m_sDateFormat; // the specified format is actually the date AND time format
$oDateTimeFormat = new DateTimeFormat($sDateTimeFormat);
$sDateFormat = $oDateTimeFormat->ToDateFormat();
AttributeDateTime::SetFormat($oDateTimeFormat);
AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));
// Translate dates from the source data
//
foreach ($this->m_aAttList as $sAttCode => $iCol)
{
if ($sAttCode == 'id') continue;
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef instanceof AttributeDateTime)
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
foreach($this->m_aData as $iRow => $aRowData)
{
$sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
if ($sNewDate !== false)
$sFormat = $sDateTimeFormat;
$sValue = $this->m_aData[$iRow][$iCol];
if (!empty($sValue))
{
// Todo - improve the reporting
$this->m_aData[$iRow][$iCol] = $sNewDate;
if ($oAttDef instanceof AttributeDate)
{
$sFormat = $sDateFormat;
}
$oFormat = new DateTimeFormat($sFormat);
$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'));
}
else
{
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
if ($oDate !== false)
{
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
$this->m_aData[$iRow][$iCol] = $sNewDate;
}
else
{
// Leave the cell unchanged
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
}
}
}
else
{
// Leave the cell unchanged
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$this->m_aData[$iRow][$iCol] = '';
}
}
}
@@ -781,8 +864,11 @@ class BulkChange
{
$aVisited = array();
}
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
foreach($this->m_aData as $iRow => $aRowData)
{
set_time_limit($iLoopTimeLimit);
if (isset($aResult[$iRow]["__STATUS__"]))
{
// An issue at the earlier steps - skip the rest
@@ -790,7 +876,7 @@ class BulkChange
}
try
{
$oReconciliationFilter = new CMDBSearchFilter($this->m_sClass);
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
$bSkipQuery = false;
foreach($this->m_aReconcilKeys as $sAttCode)
{
@@ -901,11 +987,13 @@ class BulkChange
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
set_time_limit($iLoopTimeLimit);
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
set_time_limit($iPreviousTimeLimit);
// Fill in the blanks - the result matrix is expected to be 100% complete
//
@@ -949,17 +1037,17 @@ class BulkChange
$oPage->add('<div id="'.$sAjaxDivId.'">');
}
$oPage->p(Dict::S('UI:History:BulkImports+'));
$oPage->p(Dict::S('UI:History:BulkImports+').' <span id="csv_history_reload"></span>');
$oBulkChangeSearch = DBObjectSearch::FromOQL("SELECT CMDBChange WHERE userinfo LIKE '%(CSV)'");
$oBulkChangeSearch = DBObjectSearch::FromOQL("SELECT CMDBChange WHERE origin IN ('csv-interactive', 'csv-import.php')");
$iQueryLimit = $bShowAll ? 0 : MetaModel::GetConfig()->GetMaxDisplayLimit() + 1;
$iQueryLimit = $bShowAll ? 0 : appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oBulkChanges = new DBObjectSet($oBulkChangeSearch, array('date' => false), array(), null, $iQueryLimit);
$oAppContext = new ApplicationContext();
$bLimitExceeded = false;
if ($oBulkChanges->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit())
if ($oBulkChanges->Count() > (appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit())))
{
$bLimitExceeded = true;
if (!$bShowAll)
@@ -1017,7 +1105,6 @@ class BulkChange
{
$aDetails[] = array('date' => $sDate, 'user' => $sUser, 'class' => $sClass, 'created' => $iCreated, 'modified' => $iModified);
}
}
$aConfig = array( 'date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')),
@@ -1037,7 +1124,7 @@ class BulkChange
else
{
// Truncated list
$iMinDisplayLimit = MetaModel::GetConfig()->GetMinDisplayLimit();
$iMinDisplayLimit = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oBulkChanges->Count());
$sLinkLabel = Dict::S('UI:DisplayAll');
$oPage->add('<p>'.$sCollapsedLabel.'&nbsp;&nbsp;<a class="truncated" onclick="OnTruncatedHistoryToggle(true);">'.$sLinkLabel.'</p>');
@@ -1055,6 +1142,7 @@ EOF
<<<EOF
function OnTruncatedHistoryToggle(bShowAll)
{
$('#csv_history_reload').html('<img src="../images/indicator.gif"/>');
$.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
{
$('#$sAjaxDivId').html(data);
@@ -1122,7 +1210,7 @@ EOF
{
$sAttCode = $oOperation->Get('attcode');
if (get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar')
if ((get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar') || (get_class($oOperation) == 'CMDBChangeOpSetAttributeURL'))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsExternalKey())

View File

@@ -0,0 +1,420 @@
<?php
// Copyright (C) 2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
define('EXPORTER_DEFAULT_CHUNK_SIZE', 1000);
class BulkExportException extends Exception
{
protected $sLocalizedMessage;
public function __construct($message, $sLocalizedMessage, $code = null, $previous = null)
{
parent::__construct($message, $code, $previous);
$this->sLocalizedMessage = $sLocalizedMessage;
}
public function GetLocalizedMessage()
{
return $this->sLocalizedMessage;
}
}
class BulkExportMissingParameterException extends BulkExportException
{
public function __construct($sFieldCode)
{
parent::__construct('Missing parameter: '.$sFieldCode, Dict::Format('Core:BulkExport:MissingParameter_Param', $sFieldCode));
}
}
/**
* Class BulkExport
*
* @copyright Copyright (C) 2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class BulkExportResult extends DBObject
{
public static function Init()
{
$aParams = array
(
"category" => 'core/cmdb',
"key_type" => 'autoincrement',
"name_attcode" => array('created'),
"state_attcode" => '',
"reconc_keys" => array(),
"db_table" => 'priv_bulk_export_result',
"db_key_field" => 'id',
"db_finalclass_field" => '',
"display_template" => '',
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("allowed_values"=>null, "sql"=>"user_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("chunk_size", array("allowed_values"=>null, "sql"=>"chunk_size", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("format", array("allowed_values"=>null, "sql"=>"format", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("temp_file_path", array("allowed_values"=>null, "sql"=>"temp_file_path", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("search", array("allowed_values"=>null, "sql"=>"search", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("status_info", array("allowed_values"=>null, "sql"=>"status_info", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
}
public function ComputeValues()
{
$this->Set('user_id', UserRights::GetUserId());
}
}
/**
* Garbage collector for cleaning "old" export results from the database and the disk.
* This background process runs once per day and deletes the results of all exports which
* are older than one day.
*/
class BulkExportResultGC implements iBackgroundProcess
{
public function GetPeriodicity()
{
return 24*3600; // seconds
}
public function Process($iTimeLimit)
{
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted
$sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'";
$iProcessed = 0;
while (time() < $iTimeLimit)
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
$oSet->OptimizeColumnLoad(array('temp_file_path'));
$oResult = $oSet->Fetch();
if (is_null($oResult))
{
// Nothing to be done
break;
}
$iProcessed++;
@unlink($oResult->Get('temp_file_path'));
$oResult->DBDelete();
}
return "Cleaned $iProcessed old export results(s).";
}
}
/**
* Class BulkExport
*
* @copyright Copyright (C) 2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class BulkExport
{
protected $oSearch;
protected $iChunkSize;
protected $sFormatCode;
protected $aStatusInfo;
protected $oBulkExportResult;
protected $sTmpFile;
protected $bLocalizeOutput;
public function __construct()
{
$this->oSearch = null;
$this->iChunkSize = 0;
$this->sFormatCode = null;
$this->aStatusInfo = array();
$this->oBulkExportResult = null;
$this->sTmpFile = '';
$this->bLocalizeOutput = false;
}
/**
* Find the first class capable of exporting the data in the given format
* @param string $sFormat The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
* @return iBulkExport|NULL
*/
static public function FindExporter($sFormatCode, $oSearch = null)
{
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubclassOf('BulkExport') && !$oRefClass->isAbstract())
{
$oBulkExporter = new $sPHPClass();
if ($oBulkExporter->IsFormatSupported($sFormatCode, $oSearch))
{
if ($oSearch)
{
$oBulkExporter->SetObjectList($oSearch);
}
return $oBulkExporter;
}
}
}
return null;
}
/**
* Find the exporter corresponding to the given persistent token
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
* @return iBulkExport|NULL
*/
static public function FindExporterFromToken($iPersistentToken = null)
{
$oBulkExporter = null;
$oInfo = MetaModel::GetObject('BulkExportResult', $iPersistentToken, false);
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
{
$sFormatCode = $oInfo->Get('format');
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
if ($oBulkExporter)
{
$oBulkExporter->SetFormat($sFormatCode);
$oBulkExporter->SetObjectList($oSearch);
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
$oBulkExporter->sTmpFile = $oInfo->Get('temp_file_path');
$oBulkExporter->oBulkExportResult = $oInfo;
}
}
return $oBulkExporter;
}
public function AppendToTmpFile($data)
{
if ($this->sTmpFile == '')
{
$this->sTmpFile = $this->MakeTmpFile($this->GetFileExtension());
}
$hFile = fopen($this->sTmpFile, 'ab');
if ($hFile !== false)
{
fwrite($hFile, $data);
fclose($hFile);
}
}
public function GetTmpFilePath()
{
return $this->sTmpFile;
}
/**
* Lists all possible export formats. The output is a hash array in the form: 'format_code' => 'localized format label'
* @return multitype:string
*/
static public function FindSupportedFormats()
{
$aSupportedFormats = array();
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->isSubClassOf('BulkExport') && !$oRefClass->isAbstract())
{
$oBulkExporter = new $sPHPClass;
$aFormats = $oBulkExporter->GetSupportedFormats();
$aSupportedFormats = array_merge($aSupportedFormats, $aFormats);
}
}
return $aSupportedFormats;
}
/**
* (non-PHPdoc)
* @see iBulkExport::SetChunkSize()
*/
public function SetChunkSize($iChunkSize)
{
$this->iChunkSize = $iChunkSize;
}
/**
* (non-PHPdoc)
* @see iBulkExport::SetObjectList()
*/
public function SetObjectList(DBSearch $oSearch)
{
$this->oSearch = $oSearch;
}
public function SetFormat($sFormatCode)
{
$this->sFormatCode = $sFormatCode;
}
/**
* (non-PHPdoc)
* @see iBulkExport::IsFormatSupported()
*/
public function IsFormatSupported($sFormatCode, $oSearch = null)
{
return array_key_exists($sFormatCode, $this->GetSupportedFormats());
}
/**
* (non-PHPdoc)
* @see iBulkExport::GetSupportedFormats()
*/
public function GetSupportedFormats()
{
return array(); // return array('csv' => Dict::S('UI:ExportFormatCSV'));
}
public function SetHttpHeaders(WebPage $oPage)
{
}
public function GetHeader()
{
}
abstract public function GetNextChunk(&$aStatus);
public function GetFooter()
{
}
public function SaveState()
{
if ($this->oBulkExportResult === null)
{
$this->oBulkExportResult = new BulkExportResult();
$this->oBulkExportResult->Set('format', $this->sFormatCode);
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
}
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
return $this->oBulkExportResult->DBWrite();
}
public function Cleanup()
{
if (($this->oBulkExportResult && (!$this->oBulkExportResult->IsNew())))
{
$sFilename = $this->oBulkExportResult->Get('temp_file_path');
if ($sFilename != '')
{
@unlink($sFilename);
}
$this->oBulkExportResult->DBDelete();
}
}
public function EnumFormParts()
{
return array();
}
public function DisplayFormPart(WebPage $oP, $sPartId)
{
}
public function DisplayUsage(Page $oP)
{
}
public function ReadParameters()
{
$this->bLocalizeOutput = !((bool)utils::ReadParam('no_localize', 0, true, 'integer'));
}
public function GetResultAsHtml()
{
}
public function GetRawResult()
{
}
public function GetMimeType()
{
}
public function GetFileExtension()
{
}
public function GetCharacterSet()
{
return 'UTF-8';
}
public function GetStatistics()
{
}
public function GetDownloadFileName()
{
return Dict::Format('Core:BulkExportOf_Class', MetaModel::GetName($this->oSearch->GetClass())).'.'.$this->GetFileExtension();
}
public function SetStatusInfo($aStatusInfo)
{
$this->aStatusInfo = $aStatusInfo;
}
public function GetStatusInfo()
{
return $this->aStatusInfo;
}
protected function MakeTmpFile($sExtension)
{
if(!is_dir(APPROOT."data/bulk_export"))
{
@mkdir(APPROOT."data/bulk_export", 0777, true /* recursive */);
clearstatcache();
}
if (!is_writable(APPROOT."data/bulk_export"))
{
throw new Exception('Data directory "'.APPROOT.'data/bulk_export" could not be written.');
}
$iNum = rand();
$sFileName = '';
do
{
$iNum++;
$sToken = sprintf("%08x", $iNum);
$sFileName = APPROOT."data/bulk_export/$sToken.".$sExtension;
$hFile = @fopen($sFileName, 'x');
}
while($hFile === false);
fclose($hFile);
return $sFileName;
}
}
// The built-in exports
require_once(APPROOT.'core/tabularbulkexport.class.inc.php');
require_once(APPROOT.'core/htmlbulkexport.class.inc.php');
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
require_once(APPROOT.'core/csvbulkexport.class.inc.php');
require_once(APPROOT.'core/excelbulkexport.class.inc.php');
require_once(APPROOT.'core/spreadsheetbulkexport.class.inc.php');
require_once(APPROOT.'core/xmlbulkexport.class.inc.php');

View File

@@ -44,11 +44,15 @@ class CMDBChange extends DBObject
"db_table" => "priv_change",
"db_key_field" => "id",
"db_finalclass_field" => "",
'indexes' => array(
array('origin'),
)
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum('interactive,csv-interactive,csv-import.php,webservice-soap,webservice-rest,synchro-data-source,email-processing,custom-extension'), "sql"=>"origin", "default_value"=>"interactive", "is_null_allowed"=>true, "depends_on"=>array())));
}
// Helper to keep track of the author of a given change,

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Persistent classes (internal) : cmdbChangeOp and derived
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -45,6 +45,9 @@ class CMDBChangeOp extends DBObject
"db_table" => "priv_changeop",
"db_key_field" => "id",
"db_finalclass_field" => "optype",
'indexes' => array(
array('objclass', 'objkey'),
)
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -52,7 +55,7 @@ class CMDBChangeOp extends DBObject
MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo")));
MetaModel::Init_AddAttribute(new AttributeString("objclass", array("allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("objkey", array("allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeObjectKey("objkey", array("allowed_values"=>null, "class_attcode"=>"objclass", "sql"=>"objkey", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_SetZListItems('details', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
@@ -234,7 +237,62 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
$sResult = $oAttDef->GetAsHTMLForHistory($sOldValue, $sNewValue);
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
}
return $sResult;
}
}
/**
* Record the modification of an URL
*
* @package iTopORM
*/
class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_url",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
$sResult = '';
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
}
return $sResult;
}
@@ -297,11 +355,19 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
$sAttName = $this->Get('attcode');
}
$oPrevDoc = $this->Get('prevdata');
$sDocView = $oPrevDoc->GetAsHtml();
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
if ($oPrevDoc->IsEmpty())
{
$sPrevious = '';
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevious);
}
else
{
$sDocView = $oPrevDoc->GetAsHtml();
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', $oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
}
}
return $sResult;
}
@@ -526,9 +592,6 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
$bIsHtml = true;
$sResult = '';
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
@@ -557,6 +620,66 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
}
}
/**
* Record the modification of a multiline string (text) containing some HTML markup
*
* @package iTopORM
*/
class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_html",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
$sResult = '';
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
{
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
}
else
{
// The attribute was renamed or removed from the object ?
$sAttName = $this->Get('attcode');
}
$sTextView = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$this->Get('prevdata').'</div></div>';
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
}
return $sResult;
}
}
/**
* Record the modification of a caselog (text)
* since the caselog itself stores the history
@@ -616,10 +739,20 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
// The attribute was renamed or removed from the object ?
$sAttName = $this->Get('attcode');
}
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName);
$oObj = $oMonoObjectSet->Fetch();
$oCaseLog = $oObj->Get($this->Get('attcode'));
$iMaxVisibleLength = MetaModel::getConfig()->Get('max_history_case_log_entry_length', 0);
$sTextEntry = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$oCaseLog->GetEntryAt($this->Get('lastentry')).'</div></div>';
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName, $sTextEntry);
}
return $sResult;
}
protected function ToHtml($sRawText)
{
return str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sRawText, ENT_QUOTES, 'UTF-8'));
}
}
/**
@@ -840,4 +973,70 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
return $sResult;
}
}
?>
/**
* Record the modification of custom fields
*
* @package iTopORM
*/
class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_custfields",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeLongText("prevdata", array("allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
$sResult = '';
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
{
$oTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
$aValues = json_decode($this->Get('prevdata'), true);
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
try
{
$oHandler = $oAttDef->GetHandler($aValues);
$sValueDesc = $oHandler->GetAsHTML($aValues);
}
catch (Exception $e)
{
$sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8');
}
$sTextView = '<div>'.$sValueDesc.'</div>';
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
}
}
return $sResult;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Class cmdbObject
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -46,18 +46,20 @@ require_once('stimulus.class.inc.php');
require_once('valuesetdef.class.inc.php');
require_once('MyHelpers.class.inc.php');
require_once('expression.class.inc.php');
require_once('cmdbsource.class.inc.php');
require_once('sqlquery.class.inc.php');
require_once('oql/expression.class.inc.php');
require_once('oql/oqlquery.class.inc.php');
require_once('oql/oqlexception.class.inc.php');
require_once('oql/oql-parser.php');
require_once('oql/oql-lexer.php');
require_once('oql/oqlinterpreter.class.inc.php');
require_once('cmdbsource.class.inc.php');
require_once('sqlquery.class.inc.php');
require_once('sqlobjectquery.class.inc.php');
require_once('sqlunionquery.class.inc.php');
require_once('dbobject.class.php');
require_once('dbobjectsearch.class.php');
require_once('dbsearch.class.php');
require_once('dbobjectset.class.php');
require_once('backgroundprocess.inc.php');
@@ -93,7 +95,8 @@ abstract class CMDBObject extends DBObject
// Note: this value is static, but that could be changed because it is sometimes a real issue (see update of interfaces / connected_to
protected static $m_oCurrChange = null;
protected static $m_sInfo = null; // null => the information is built in a standard way
protected static $m_sOrigin = null; // null => the origin is 'interactive'
/**
* Specify another change (this is mainly for backward compatibility)
*/
@@ -134,6 +137,15 @@ abstract class CMDBObject extends DBObject
self::$m_sInfo = $sInfo;
}
/**
* Provides information about the origin of the change
* @param $sOrigin String: one of: interactive, csv-interactive, csv-import.php, webservice-soap, webservice-rest, syncho-data-source, email-processing, custom-extension
*/
public static function SetTrackOrigin($sOrigin)
{
self::$m_sOrigin = $sOrigin;
}
/**
* Get the additional information (defaulting to user name)
*/
@@ -148,7 +160,22 @@ abstract class CMDBObject extends DBObject
return self::$m_sInfo;
}
}
/**
* Get the 'origin' information (defaulting to 'interactive')
*/
protected static function GetTrackOrigin()
{
if (is_null(self::$m_sOrigin))
{
return 'interactive';
}
else
{
return self::$m_sOrigin;
}
}
/**
* Create a standard change record (done here 99% of the time, and nearly once per page)
*/
@@ -157,11 +184,27 @@ abstract class CMDBObject extends DBObject
self::$m_oCurrChange = MetaModel::NewObject("CMDBChange");
self::$m_oCurrChange->Set("date", time());
self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo());
self::$m_oCurrChange->Set("origin", self::GetTrackOrigin());
self::$m_oCurrChange->DBInsert();
}
protected function RecordObjCreation()
{
// Delete any existing change tracking about the current object (IDs can be reused due to InnoDb bug; see TRAC #886)
//
// 1 - remove the deletion record(s)
// Note that objclass contain the ROOT class
$oFilter = new DBObjectSearch('CMDBChangeOpDelete');
$oFilter->AddCondition('objclass', MetaModel::GetRootClass(get_class($this)), '=');
$oFilter->AddCondition('objkey', $this->GetKey(), '=');
MetaModel::PurgeData($oFilter);
// 2 - any other change tracking information left prior to 2.0.3 (when the purge of the history has been implemented in RecordObjDeletion
// In that case, objclass is the final class of the object
$oFilter = new DBObjectSearch('CMDBChangeOp');
$oFilter->AddCondition('objclass', get_class($this), '=');
$oFilter->AddCondition('objkey', $this->GetKey(), '=');
MetaModel::PurgeData($oFilter);
parent::RecordObjCreation();
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");
$oMyChangeOp->Set("objclass", get_class($this));
@@ -171,12 +214,20 @@ abstract class CMDBObject extends DBObject
protected function RecordObjDeletion($objkey)
{
$sRootClass = MetaModel::GetRootClass(get_class($this));
// Delete any existing change tracking about the current object
$oFilter = new DBObjectSearch('CMDBChangeOp');
$oFilter->AddCondition('objclass', get_class($this), '=');
$oFilter->AddCondition('objkey', $objkey, '=');
MetaModel::PurgeData($oFilter);
parent::RecordObjDeletion($objkey);
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpDelete");
$oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this)));
$oMyChangeOp->Set("objkey", $objkey);
$oMyChangeOp->Set("fclass", get_class($this));
$oMyChangeOp->Set("fname", $this->GetRawName());
$oMyChangeOp->Set("fname", substr($this->GetRawName(), 0, 255)); // Protect against very long friendly names
$iId = $oMyChangeOp->DBInsertNoReload();
}
@@ -189,8 +240,9 @@ abstract class CMDBObject extends DBObject
foreach ($aValues as $sAttCode=> $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalField()) continue; // #@# temporary
if ($oAttDef->IsLinkSet()) continue; // #@# temporary
if ($oAttDef->IsExternalField()) continue;
if ($oAttDef->IsLinkSet()) continue;
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) continue;
if (array_key_exists($sAttCode, $aOrigValues))
{
@@ -250,14 +302,10 @@ abstract class CMDBObject extends DBObject
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
if (is_null($original))
{
$original = new OrmStopWatch();
}
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
{
$item_value = $oSubItemAttDef->GetValue($value);
$item_original = $oSubItemAttDef->GetValue($original);
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
if ($item_value != $item_original)
{
@@ -285,7 +333,14 @@ abstract class CMDBObject extends DBObject
elseif ($oAttDef instanceOf AttributeLongText)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -300,7 +355,14 @@ abstract class CMDBObject extends DBObject
elseif ($oAttDef instanceOf AttributeText)
{
// Data blobs
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
if ($oAttDef->GetFormat() == 'html')
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
}
else
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
}
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
@@ -334,6 +396,29 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeCustomFields)
{
// Custom fields
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeURL)
{
// URLs
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
else
{
// Scalars
@@ -378,38 +463,38 @@ abstract class CMDBObject extends DBObject
}
public function DBInsert()
{
return $this->DBInsertTracked_Internal();
}
public function DBInsertTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal();
return $ret;
}
public function DBInsertTrackedNoReload(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal(true);
return $ret;
}
/**
* To Be Obsoleted: DO NOT rely on an overload of this method since
* DBInsertTracked (resp. DBInsertTrackedNoReload) may call directly
* DBInsert (resp. DBInsertNoReload) in future versions of iTop.
* @param bool $bDoNotReload
* @return integer Identifier of the created object
*/
protected function DBInsertTracked_Internal($bDoNotReload = false)
{
if ($bDoNotReload)
{
$ret = parent::DBInsertNoReload();
$ret = $this->DBInsertNoReload();
}
else
{
$ret = parent::DBInsert();
$ret = $this->DBInsert();
}
return $ret;
}
@@ -472,18 +557,18 @@ abstract class CMDBObject extends DBObject
return $ret;
}
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
public static function BulkUpdate(DBSearch $oFilter, array $aValues)
{
return $this->BulkUpdateTracked_Internal($oFilter, $aValues);
return static::BulkUpdateTracked_Internal($oFilter, $aValues);
}
public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues)
{
self::SetCurrentChange($oChange);
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
static::BulkUpdateTracked_Internal($oFilter, $aValues);
}
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues)
{
// $aValues is an array of $sAttCode => $value
@@ -530,7 +615,7 @@ class CMDBObjectSet extends DBObjectSet
static public function FromScratch($sClass)
{
$oFilter = new CMDBSearchFilter($sClass);
$oFilter = new DBObjectSearch($sClass);
$oFilter->AddConditionExpression(new FalseExpression());
$oRetSet = new self($oFilter);
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private in the base class (and you will not get any error message)
@@ -554,7 +639,7 @@ class CMDBObjectSet extends DBObjectSet
// let's create one search definition
$sClass = reset($aClasses);
$sAlias = key($aClasses);
$oFilter = new CMDBSearchFilter($sClass, $sAlias);
$oFilter = new DBObjectSearch($sClass, $sAlias);
$oRetSet = new CMDBObjectSet($oFilter);
$oRetSet->m_bLoaded = true; // no DB load
@@ -566,16 +651,3 @@ class CMDBObjectSet extends DBObjectSet
return $oRetSet;
}
}
/**
* TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design!
*
* @package iTopORM
*/
class CMDBSearchFilter extends DBObjectSearch
{
// this is the public interface (?)
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* DB Server abstraction
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -29,10 +29,18 @@ require_once(APPROOT.'core/kpi.class.inc.php');
class MySQLException extends CoreException
{
public function __construct($sIssue, $aContext)
public function __construct($sIssue, $aContext, $oException = null)
{
$aContext['mysql_error'] = CMDBSource::GetError();
$aContext['mysql_errno'] = CMDBSource::GetErrNo();;
if ($oException != null)
{
$aContext['mysql_error'] = $oException->getCode();
$aContext['mysql_errno'] = $oException->getMessage();
}
else
{
$aContext['mysql_error'] = CMDBSource::GetError();
$aContext['mysql_errno'] = CMDBSource::GetErrNo();
}
parent::__construct($sIssue, $aContext);
}
}
@@ -50,7 +58,7 @@ class CMDBSource
protected static $m_sDBUser;
protected static $m_sDBPwd;
protected static $m_sDBName;
protected static $m_resDBLink;
protected static $m_oMysqli;
public static function Init($sServer, $sUser, $sPwd, $sSource = '')
{
@@ -58,15 +66,41 @@ class CMDBSource
self::$m_sDBUser = $sUser;
self::$m_sDBPwd = $sPwd;
self::$m_sDBName = $sSource;
if (!self::$m_resDBLink = @mysqli_connect($sServer, $sUser, $sPwd))
self::$m_oMysqli = null;
mysqli_report(MYSQLI_REPORT_STRICT); // *some* errors (like connection errors) will throw mysqli_sql_exception instead
// of generating warnings printed to the output but some other errors will still
// cause the query() method to return false !!!
try
{
throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer, 'user'=>$sUser));
$aConnectInfo = explode(':', self::$m_sDBHost);
if (count($aConnectInfo) > 1)
{
// Override the default port
$sServer = $aConnectInfo[0];
$iPort = (int)$aConnectInfo[1];
self::$m_oMysqli = new mysqli($sServer, self::$m_sDBUser, self::$m_sDBPwd, '', $iPort);
}
else
{
self::$m_oMysqli = new mysqli(self::$m_sDBHost, self::$m_sDBUser, self::$m_sDBPwd);
}
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Could not connect to the DB server', array('host'=>self::$m_sDBHost, 'user'=>self::$m_sDBUser), $e);
}
if (!empty($sSource))
{
if (!((bool)mysqli_query(self::$m_resDBLink, "USE `$sSource`")))
try
{
throw new MySQLException('Could not select DB', array('host'=>$sServer, 'user'=>$sUser, 'db_name'=>$sSource));
mysqli_report(MYSQLI_REPORT_STRICT); // Errors, in the next query, will throw mysqli_sql_exception
self::$m_oMysqli->query("USE `$sSource`");
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Could not select DB', array('host'=>self::$m_sDBHost, 'user'=>self::$m_sDBUser, 'db_name'=>self::$m_sDBName), $e);
}
}
}
@@ -120,7 +154,7 @@ class CMDBSource
{
// In case we don't have rights to enumerate the databases
// Let's try to connect directly
return @((bool)mysqli_query(self::$m_resDBLink, "USE `$sSource`"));
return @((bool)self::$m_oMysqli->query("USE `$sSource`"));
}
}
@@ -133,7 +167,7 @@ class CMDBSource
public static function SelectDB($sSource)
{
if (!((bool)mysqli_query(self::$m_resDBLink, "USE `$sSource`")))
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
{
throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
}
@@ -175,25 +209,25 @@ class CMDBSource
public static function GetErrNo()
{
if (self::$m_resDBLink)
if (self::$m_oMysqli->errno != 0)
{
return mysqli_errno(self::$m_resDBLink);
return self::$m_oMysqli->errno;
}
else
{
return mysqli_connect_errno();
return self::$m_oMysqli->connect_errno;
}
}
public static function GetError()
{
if (self::$m_resDBLink)
if (self::$m_oMysqli->error != '')
{
return mysqli_error(self::$m_resDBLink);
return self::$m_oMysqli->error;
}
else
{
return mysqli_connect_error();
return self::$m_oMysqli->connect_error;
}
}
@@ -234,42 +268,43 @@ class CMDBSource
// Quote if not a number or a numeric string
if ($bAlways || is_string($value))
{
$value = $cQuoteStyle . mysqli_real_escape_string(self::$m_resDBLink, $value) . $cQuoteStyle;
$value = $cQuoteStyle . self::$m_oMysqli->real_escape_string($value) . $cQuoteStyle;
}
return $value;
}
public static function Query($sSQLQuery)
{
// Add info into the query as a comment, for easier error tracking
// disabled until we need it really!
//
//$aTraceInf['file'] = __FILE__;
// $sSQLQuery .= MyHelpers::MakeSQLComment($aTraceInf);
$oKPI = new ExecutionKPI();
$result = mysqli_query(self::$m_resDBLink, $sSQLQuery);
if (!$result)
try
{
$oResult = self::$m_oMysqli->query($sSQLQuery);
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSQLQuery);
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSQLQuery);
return $result;
return $oResult;
}
public static function GetNextInsertId($sTable)
{
$sSQL = "SHOW TABLE STATUS LIKE '$sTable'";
$result = self::Query($sSQL);
$aRow = mysqli_fetch_assoc($result);
$oResult = self::Query($sSQL);
$aRow = $oResult->fetch_assoc();
$iNextInsertId = $aRow['Auto_increment'];
return $iNextInsertId;
}
public static function GetInsertId()
{
$iRes = mysqli_insert_id(self::$m_resDBLink);
$iRes = self::$m_oMysqli->insert_id;
if (is_null($iRes))
{
return 0;
@@ -293,37 +328,59 @@ class CMDBSource
public static function QueryToScalar($sSql)
{
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
}
catch(mysqli_sql_exception $e)
{
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH))
if ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
{
$res = $aRow[0];
}
else
{
mysqli_free_result($result);
$oResult->free();
throw new MySQLException('Found no result for query', array('query' => $sSql));
}
mysqli_free_result($result);
$oResult->free();
return $res;
}
public static function QueryToArray($sSql)
{
$aData = array();
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
}
catch(mysqli_sql_exception $e)
{
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
while ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH))
while ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
{
$aData[] = $aRow;
}
mysqli_free_result($result);
$oResult->free();
return $aData;
}
@@ -341,56 +398,73 @@ class CMDBSource
public static function ExplainQuery($sSql)
{
$aData = array();
$result = mysqli_query(self::$m_resDBLink, "EXPLAIN $sSql");
if (!$result)
try
{
$oResult = self::$m_oMysqli->query($sSql);
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
$aNames = self::GetColumns($result);
$aNames = self::GetColumns($oResult);
$aData[] = $aNames;
while ($aRow = mysqli_fetch_array($result, MYSQLI_ASSOC))
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
{
$aData[] = $aRow;
}
mysqli_free_result($result);
$oResult->free();
return $aData;
}
public static function TestQuery($sSql)
{
$result = mysqli_query(self::$m_resDBLink, "EXPLAIN $sSql");
if (!$result)
try
{
return self::GetError();
$oResult = self::$m_oMysqli->query($sSql);
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
mysqli_free_result($result);
if (is_object($oResult))
{
$oResult->free();
}
return '';
}
public static function NbRows($result)
public static function NbRows($oResult)
{
return mysqli_num_rows($result);
return $oResult->num_rows;
}
public static function AffectedRows()
{
return mysqli_affected_rows(self::$m_resDBLink);
return self::$m_oMysqli->affected_rows;
}
public static function FetchArray($result)
public static function FetchArray($oResult)
{
return mysqli_fetch_array($result, MYSQLI_ASSOC);
return $oResult->fetch_array(MYSQLI_ASSOC);
}
public static function GetColumns($result)
public static function GetColumns($oResult)
{
$aNames = array();
for ($i = 0; $i < (($___mysqli_tmp = mysqli_num_fields($result)) ? $___mysqli_tmp : 0) ; $i++)
for ($i = 0; $i < (($___mysqli_tmp = $oResult->field_count) ? $___mysqli_tmp : 0) ; $i++)
{
$meta = mysqli_fetch_field_direct($result, $i);
$meta = $oResult->fetch_field_direct($i);
if (!$meta)
{
throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i));
@@ -403,14 +477,15 @@ class CMDBSource
return $aNames;
}
public static function Seek($result, $iRow)
public static function Seek($oResult, $iRow)
{
return mysqli_data_seek($result, $iRow);
return $oResult->data_seek($iRow);
}
public static function FreeResult($result)
public static function FreeResult($oResult)
{
return ((mysqli_free_result($result) || (is_object($result) && (get_class($result) == "mysqli_result"))) ? true : false);
$oResult->free(); /* returns void */
return true;
}
public static function IsTable($sTable)
@@ -466,14 +541,54 @@ class CMDBSource
return ($aFieldData["Type"]);
}
public static function HasIndex($sTable, $sField)
public static function GetFieldSpec($sTable, $sField)
{
$aTableInfo = self::GetTableInfo($sTable);
if (empty($aTableInfo)) return false;
if (!array_key_exists($sField, $aTableInfo["Fields"])) return false;
$aFieldData = $aTableInfo["Fields"][$sField];
// $aFieldData could be 'PRI' for the primary key, or 'MUL', or ?
return (strlen($aFieldData["Key"]) > 0);
$sRet = $aFieldData["Type"];
if ($aFieldData["Null"] == 'NO')
{
$sRet .= ' NOT NULL';
}
if (is_numeric($aFieldData["Default"]))
{
if (strtolower(substr($aFieldData["Type"], 0, 5)) == 'enum(')
{
// Force quotes to match the column declaration statement
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"], true);
}
else
{
$default = $aFieldData["Default"] + 0; // Coerce to a numeric variable
$sRet .= ' DEFAULT '.self::Quote($default);
}
}
elseif (is_string($aFieldData["Default"]) == 'string')
{
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]);
}
return $sRet;
}
public static function HasIndex($sTable, $sIndexId, $aFields = null)
{
$aTableInfo = self::GetTableInfo($sTable);
if (empty($aTableInfo)) return false;
if (!array_key_exists($sIndexId, $aTableInfo['Indexes'])) return false;
if ($aFields == null)
{
// Just searching for the name
return true;
}
// Compare the columns
$sSearchedIndex = implode(',', $aFields);
$sExistingIndex = implode(',', $aTableInfo['Indexes'][$sIndexId]);
return ($sSearchedIndex == $sExistingIndex);
}
// Returns an array of (fieldname => array of field info)
@@ -523,6 +638,17 @@ class CMDBSource
// Table does not exist
self::$m_aTablesInfo[strtolower($sTableName)] = null;
}
if (!is_null(self::$m_aTablesInfo[strtolower($sTableName)]))
{
$aIndexes = self::QueryToArray("SHOW INDEXES FROM `$sTableName`");
$aMyIndexes = array();
foreach ($aIndexes as $aIndexColumn)
{
$aMyIndexes[$aIndexColumn['Key_name']][$aIndexColumn['Seq_in_index']-1] = $aIndexColumn['Column_name'];
}
self::$m_aTablesInfo[strtolower($sTableName)]["Indexes"] = $aMyIndexes;
}
}
//public static function EnumTables()
//{
@@ -548,18 +674,25 @@ class CMDBSource
public static function DumpTable($sTable)
{
$sSql = "SELECT * FROM `$sTable`";
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
try
{
$oResult = self::$m_oMysqli->query($sSql);
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql), $e);
}
if ($oResult === false)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
$aRows = array();
while ($aRow = mysqli_fetch_array($result, MYSQLI_ASSOC))
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
{
$aRows[] = $aRow;
}
mysqli_free_result($result);
$oResult->free();
return $aRows;
}
@@ -589,7 +722,7 @@ class CMDBSource
{
try
{
$result = self::Query('SHOW GRANTS'); // [ FOR CURRENT_USER()]
$oResult = self::Query('SHOW GRANTS'); // [ FOR CURRENT_USER()]
}
catch(MySQLException $e)
{
@@ -597,12 +730,12 @@ class CMDBSource
}
$aRes = array();
while ($aRow = mysqli_fetch_array($result, MYSQLI_NUM))
while ($aRow = $oResult->fetch_array(MYSQLI_NUM))
{
// so far, only one column...
$aRes[] = implode('/', $aRow);
}
mysqli_free_result($result);
$oResult->free();
// so far, only one line...
return implode(', ', $aRes);
}
@@ -615,21 +748,21 @@ class CMDBSource
{
try
{
$result = self::Query('SHOW SLAVE STATUS');
$oResult = self::Query('SHOW SLAVE STATUS');
}
catch(MySQLException $e)
{
throw new CoreException("Current user not allowed to check the status", array('mysql_error' => $e->getMessage()));
}
if (mysqli_num_rows($result) == 0)
if ($oResult->num_rows == 0)
{
return false;
}
// Returns one single row anytime
$aRow = mysqli_fetch_array($result, MYSQLI_ASSOC);
mysqli_free_result($result);
$aRow = $oResult->fetch_array(MYSQLI_ASSOC);
$oResult->free();
if (!isset($aRow['Slave_IO_Running']))
{
@@ -651,7 +784,4 @@ class CMDBSource
}
return false;
}
}
?>
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2014 Combodo SARL
//
// This file is part of iTop.
//
@@ -96,11 +96,19 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate)
{
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::Trace(WorkingTimeRecorder::TRACE_DEBUG, __class__.'::'.__function__);
}
//echo "GetDeadline - default: ".$oStartDate->format('Y-m-d H:i:s')." + $iDuration<br/>\n";
// Default implementation: 24x7, no holidays: to compute the deadline, just add
// the specified duration to the given date/time
$oResult = clone $oStartDate;
$oResult->modify('+'.$iDuration.' seconds');
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::SetValues($oStartDate->format('U'), $oResult->format('U'), $iDuration, WorkingTimeRecorder::COMPUTED_END);
}
return $oResult;
}
@@ -113,8 +121,17 @@ class DefaultWorkingTimeComputer implements iWorkingTimeComputer
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
{
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::Trace(WorkingTimeRecorder::TRACE_DEBUG, __class__.'::'.__function__);
}
//echo "GetOpenDuration - default: ".$oStartDate->format('Y-m-d H:i:s')." to ".$oEndDate->format('Y-m-d H:i:s')."<br/>\n";
return abs($oEndDate->format('U') - $oStartDate->format('U'));
$iDuration = abs($oEndDate->format('U') - $oStartDate->format('U'));
if (class_exists('WorkingTimeRecorder'))
{
WorkingTimeRecorder::SetValues($oStartDate->format('U'), $oEndDate->format('U'), $iDuration, WorkingTimeRecorder::COMPUTED_DURATION);
}
return $iDuration;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -30,11 +30,12 @@ define('ACCESS_READONLY', 0);
/**
* Configuration read/write
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once('coreexception.class.inc.php');
require_once('attributedef.class.inc.php'); // For the defines
class ConfigException extends CoreException
{
@@ -47,8 +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_KPI_DURATION', false);
define ('DEFAULT_LOG_KPI_MEMORY', false);
define ('DEFAULT_LOG_QUERIES', false);
define ('DEFAULT_QUERY_CACHE_ENABLED', true);
@@ -78,7 +77,6 @@ class Config
protected $m_aDataModels;
protected $m_aWebServiceCategories;
protected $m_aAddons;
protected $m_aDictionaries;
protected $m_aModuleSettings;
@@ -149,13 +147,21 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'disable_mandatory_ext_keys' => array(
'type' => 'bool',
'description' => 'For developpers: allow every external keys to be undefined',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'graphviz_path' => array(
'type' => 'string',
'description' => 'Path to the Graphviz "dot" executable for graphing objects lifecycle',
'default' => '/usr/bin/dot',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
'show_in_conf_sample' => true,
),
'php_path' => array(
'type' => 'string',
@@ -270,6 +276,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_rest_service' => array(
'type' => 'bool',
'description' => 'Log the usage of the REST/JSON service',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'synchro_trace' => array(
'type' => 'string',
'description' => 'Synchronization details: none, display, save (includes \'display\')',
@@ -326,6 +340,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'async_task_retries' => array(
'type' => 'array',
'description' => 'Automatic retries of asynchronous tasks in case of failure (per class)',
'default' => array('AsyncSendEmail' => array('max_retries' => 0, 'retry_delay' => 600)),
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_asynchronous' => array(
'type' => 'bool',
'description' => 'If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode',
@@ -382,6 +404,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_css' => array(
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'apc_cache.enabled' => array(
'type' => 'bool',
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
@@ -525,6 +555,24 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'forgot_password' => array(
'type' => 'bool',
'description' => 'Enable the "Forgot password" feature',
// examples... not used (nor 'description')
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'forgot_password_from' => array(
'type' => 'string',
'description' => 'Sender email address for the "forgot password" feature. If empty, defaults to the recipient\'s email address.',
// examples... not used (nor 'description')
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'deadline_format' => array(
'type' => 'string',
'description' => 'The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$',
@@ -606,6 +654,282 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'url_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)',
'default' => '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\$_.-]*)?',
// SHEME.......... USER....................... PASSWORD.......................... HOST/IP........... PORT.......... PATH........................ GET............................................ ANCHOR............................
// Example: http://User:passWord@127.0.0.1:8888/patH/Page.php?arrayArgument[2]=something:blah20#myAnchor
// Origin of this regexp: http://www.php.net/manual/fr/function.preg-match.php#93824
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_validation_pattern' => array(
'type' => 'string',
'description' => 'Regular expression to validate/detect the format of an eMail address',
'default' => "[a-zA-Z0-9._&'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}",
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'log_kpi_duration' => array(
'type' => 'integer',
'description' => 'Level of logging for troubleshooting performance issues (1 to enable, 2 +blame callers)',
// examples... not used
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_kpi_memory' => array(
'type' => 'integer',
'description' => 'Level of logging for troubleshooting memory limit issues',
// examples... not used
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_kpi_user_id' => array(
'type' => 'string',
'description' => 'Limit the scope of users to the given user id (* means no limit)',
// examples... not used
'default' => '*',
'value' => '*',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'max_linkset_output' => array(
'type' => 'integer',
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
'default' => 100,
'value' => 100,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'demo_mode' => array(
'type' => 'bool',
'description' => 'Set to true to prevent users from changing passwords/languages',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'portal_tickets' => array(
'type' => 'string',
'description' => 'CSV list of classes supported in the portal',
// examples... not used
'default' => 'UserRequest',
'value' => 'UserRequest',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'portal_dispatch_urls' => array(
'type' => 'array',
'description' => 'Associative array of sPortalId => Home page URL (relatively to the application root)',
// examples... not used
'default' => array(),
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'max_execution_time_per_loop' => array(
'type' => 'integer',
'description' => 'Maximum execution time requested, per loop, during bulk operations. Zero means no limit.',
// examples... not used
'default' => 30,
'value' => 30,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'max_history_length' => array(
'type' => 'integer',
'description' => 'Maximum length of the history table (in the "History" tab on each object) before it gets truncated. Latest modifications are displayed first.',
// examples... not used
'default' => 50,
'value' => 50,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'max_history_case_log_entry_length' => array(
'type' => 'integer',
'description' => 'The length (in number of characters) at which to truncate the (expandable) display (in the history) of a case log entry. If zero, the display in the history is not truncated.',
// examples... not used
'default' => 60,
'value' => 60,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'full_text_chunk_duration' => array(
'type' => 'integer',
'description' => 'Delay after which the results are displayed.',
// examples... not used
'default' => 2,
'value' => 2,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'full_text_accelerators' => array(
'type' => 'array',
'description' => 'Specifies classes to be searched at first (and the subset of data) when running the full text search.',
'default' => array(),
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'full_text_needle_min' => array(
'type' => 'integer',
'description' => 'Minimum size of the full text needle.',
'default' => 3,
'value' => 3,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'tracking_level_linked_set_default' => array(
'type' => 'integer',
'description' => 'Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)',
'default' => LINKSET_TRACKING_LIST,
'value' => LINKSET_TRACKING_LIST,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'tracking_level_linked_set_indirect_default' => array(
'type' => 'integer',
'description' => 'Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSetIndirect',
'default' => LINKSET_TRACKING_ALL,
'value' => LINKSET_TRACKING_ALL,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'user_rights_legacy' => array(
'type' => 'bool',
'description' => 'Set to true to restore the buggy algorithm for the computation of user rights (within the same profile, ALLOW on the class itself has precedence on DENY of a parent class)',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'xlsx_exporter_memory_limit' => array(
'type' => 'string',
'description' => 'Memory limit to use when (interactively) exporting data to Excel',
'default' => '2048M', // Huuuuuuge 2GB!
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'min_reload_interval' => array(
'type' => 'integer',
'description' => 'Minimum refresh interval (seconds) for dashboards, shortcuts, etc. Even if the interval is set programmatically, it is forced to that minimum',
'default' => 5, // In iTop 2.0.3, this was the hardcoded value
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'relations_max_depth' => array(
'type' => 'integer',
'description' => 'Maximum number of successive levels (depth) to explore when displaying the impact/depends on relations.',
'default' => 20, // In iTop 2.0.3, this was the hardcoded value
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transaction_storage' => array(
'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
'default' => 'Session',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transactions_enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the whole mechanism to prevent multiple submissions of a page is enabled.',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_transactions' => array(
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'concurrent_lock_enabled' => array(
'type' => 'bool',
'description' => 'Whether or not to activate the locking mechanism in order to prevent concurrent edition of the same object.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'concurrent_lock_expiration_delay' => array(
'type' => 'integer',
'description' => 'Delay (in seconds) for a concurrent lock to expire',
'default' => 120,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'concurrent_lock_override_profiles' => array(
'type' => 'array',
'description' => 'The list of profiles allowed to "kill" a lock',
'default' => array('Administrator'),
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'html_sanitizer' => array(
'type' => 'string',
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
'default' => 'HTMLDOMSanitizer',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'inline_image_max_display_width' => array(
'type' => 'integer',
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
'default' => '250',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'inline_image_max_storage_width' => array(
'type' => 'integer',
'description' => 'The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.',
'default' => '1600',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'date_and_time_format' => array(
'type' => 'array',
'description' => 'Format for date and time display (per language)',
'default' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')),
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'breadcrumb.max_count' => array(
'type' => 'integer',
'description' => 'Maximum number of items kept in the history breadcrumb. Set it to 0 to entirely disable the breadcrumb.',
'default' => 8,
'value' => 8,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'legacy_search_drawer_open' => array(
'type' => 'bool',
'description' => 'Whether or not to display the "search drawer" open by default as in previous versions of iTop.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)
@@ -634,6 +958,8 @@ class Config
case 'float':
$value = (float) $value;
break;
case 'array':
break;
default:
throw new CoreException('Unknown type for setting', array('property' => $sPropCode, 'type' => $sType));
}
@@ -664,8 +990,6 @@ class Config
protected $m_bLogNotification;
protected $m_bLogIssue;
protected $m_bLogWebService;
protected $m_bLogKPIDuration; // private setting
protected $m_bLogKPIMemory; // private setting
protected $m_bLogQueries; // private setting
protected $m_bQueryCacheEnabled; // private setting
@@ -730,33 +1054,10 @@ class Config
$bLoadConfig = false;
}
$this->m_aAppModules = array(
// Some default modules, always present can be move to an official iTop Module later if needed
'application/transaction.class.inc.php',
'application/menunode.class.inc.php',
'application/user.preferences.class.inc.php',
'application/user.dashboard.class.inc.php',
'application/audit.rule.class.inc.php',
'application/query.class.inc.php',
// Romain - That's dirty, because those classes are in fact part of the core
// but I needed those classes to be derived from cmdbAbstractObject
// (to be managed via the GUI) and this class in not really known from
// the core, PLUS I needed the includes to be there also for the setup
// to create the tables.
'core/event.class.inc.php',
'core/action.class.inc.php',
'core/trigger.class.inc.php',
'synchro/synchrodatasource.class.inc.php',
);
$this->m_aDataModels = array();
$this->m_aWebServiceCategories = array(
'webservices/webservices.basic.php',
);
$this->m_aAddons = array(
// Default AddOn, always present can be moved to an official iTop Module later if needed
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
);
$this->m_aDictionaries = self::ScanDictionariesDir();
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
{
@@ -774,8 +1075,6 @@ class Config
$this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION;
$this->m_bLogIssue = DEFAULT_LOG_ISSUE;
$this->m_bLogWebService = DEFAULT_LOG_WEB_SERVICE;
$this->m_bLogKPIDuration = DEFAULT_LOG_KPI_DURATION;
$this->m_bLogKPIDuration = DEFAULT_LOG_KPI_DURATION;
$this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT;
$this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT;
$this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL;
@@ -786,6 +1085,8 @@ 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();
@@ -855,18 +1156,7 @@ class Config
{
throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings'));
}
if (!isset($MyModules) || !is_array($MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules'));
}
if (!array_key_exists('application', $MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'application\']'));
}
if (!array_key_exists('business', $MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'business\']'));
}
if (!array_key_exists('addons', $MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
@@ -876,24 +1166,21 @@ class Config
// Add one, by default
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
}
if (!array_key_exists('dictionaries', $MyModules))
{
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'dictionaries\']'));
}
$this->m_aAppModules = $MyModules['application'];
$this->m_aDataModels = $MyModules['business'];
if (isset($MyModules['webservices']))
{
$this->m_aWebServiceCategories = $MyModules['webservices'];
}
$this->m_aAddons = $MyModules['addons'];
$this->m_aDictionaries = $MyModules['dictionaries'];
foreach($MySettings as $sPropCode => $rawvalue)
{
if ($this->IsProperty($sPropCode))
{
$value = trim($rawvalue);
if (is_string($rawvalue))
{
$value = trim($rawvalue);
}
else
{
$value = $rawvalue;
}
$this->Set($sPropCode, $value, $sConfigFile);
}
}
@@ -911,8 +1198,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_bLogKPIDuration = isset($MySettings['log_kpi_duration']) ? (bool) trim($MySettings['log_kpi_duration']) : DEFAULT_LOG_KPI_DURATION;
$this->m_bLogKPIMemory = isset($MySettings['log_kpi_memory']) ? (bool) trim($MySettings['log_kpi_memory']) : DEFAULT_LOG_KPI_MEMORY;
$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;
@@ -943,41 +1228,29 @@ class Config
{
return $this->m_aModuleSettings[$sModule][$sProperty];
}
return $defaultvalue;
// Fall back to the predefined XML parameter, if any
return $this->GetModuleParameter($sModule, $sProperty, $defaultvalue);
}
public function GetModuleParameter($sModule, $sProperty, $defaultvalue = null)
{
$ret = $defaultvalue;
if (class_exists('ModulesXMLParameters'))
{
$aAllParams = ModulesXMLParameters::GetData($sModule);
if(array_key_exists($sProperty, $aAllParams))
{
$ret = $aAllParams[$sProperty];
}
}
return $ret;
}
public function SetModuleSetting($sModule, $sProperty, $value)
{
$this->m_aModuleSettings[$sModule][$sProperty] = $value;
}
public function GetAppModules()
{
return $this->m_aAppModules;
}
public function SetAppModules($aAppModules)
{
$this->m_aAppModules = $aAppModules;
}
public function GetDataModels()
{
return $this->m_aDataModels;
}
public function SetDataModels($aDataModels)
{
$this->m_aDataModels = $aDataModels;
}
public function GetWebServiceCategories()
{
return $this->m_aWebServiceCategories;
}
public function SetWebServiceCategories($aWebServiceCategories)
{
$this->m_aWebServiceCategories = $aWebServiceCategories;
}
public function GetAddons()
{
return $this->m_aAddons;
@@ -987,15 +1260,6 @@ class Config
$this->m_aAddons = $aAddons;
}
public function GetDictionaries()
{
return $this->m_aDictionaries;
}
public function SetDictionaries($aDictionaries)
{
$this->m_aDictionaries = $aDictionaries;
}
public function GetDBHost()
{
return $this->m_sDBHost;
@@ -1051,16 +1315,6 @@ class Config
return $this->m_bLogWebService;
}
public function GetLogKPIDuration()
{
return $this->m_bLogKPIDuration;
}
public function GetLogKPIMemory()
{
return $this->m_bLogKPIMemory;
}
public function GetLogQueries()
{
return $this->m_bLogQueries;
@@ -1265,6 +1519,8 @@ 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;
$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
@@ -1272,6 +1528,7 @@ class Config
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
$aSettings['default_language'] = $this->m_sDefaultLanguage;
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
@@ -1282,26 +1539,10 @@ class Config
$aSettings['module_settings'][$sModule][$sProperty] = $value;
}
}
foreach($this->m_aAppModules as $sFile)
{
$aSettings['application_list'][] = $sFile;
}
foreach($this->m_aDataModels as $sFile)
{
$aSettings['datamodel_list'][] = $sFile;
}
foreach($this->m_aWebServiceCategories as $sFile)
{
$aSettings['webservice_list'][] = $sFile;
}
foreach($this->m_aAddons as $sKey => $sFile)
{
$aSettings['addon_list'][] = $sFile;
}
foreach($this->m_aDictionaries as $sFile)
{
$aSettings['dictionary_list'][] = $sFile;
}
return $aSettings;
}
@@ -1337,6 +1578,8 @@ 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,
);
foreach($aBoolValues as $sKey => $bValue)
@@ -1375,6 +1618,7 @@ class Config
'db_collation' => $this->m_sDBCollation,
'default_language' => $this->m_sDefaultLanguage,
'allowed_login_types' => $this->m_sAllowedLoginTypes,
'ext_auth_variable' => $this->m_sExtAuthVariable,
'encryption_key' => $this->m_sEncryptionKey,
'csv_import_charsets' => $this->m_aCharsets,
);
@@ -1443,36 +1687,12 @@ class Config
fwrite($hFile, " *\n");
fwrite($hFile, " */\n");
fwrite($hFile, "\$MyModules = array(\n");
fwrite($hFile, "\t'application' => array (\n");
foreach($this->m_aAppModules as $sFile)
{
fwrite($hFile, "\t\t'$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, "\t'business' => array (\n");
foreach($this->m_aDataModels as $sFile)
{
fwrite($hFile, "\t\t'$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, "\t'webservices' => array (\n");
foreach($this->m_aWebServiceCategories as $sFile)
{
fwrite($hFile, "\t\t'$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, "\t'addons' => array (\n");
foreach($this->m_aAddons as $sKey => $sFile)
{
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, "\t'dictionaries' => array (\n");
foreach($this->m_aDictionaries as $sFile)
{
fwrite($hFile, "\t\t'$sFile',\n");
}
fwrite($hFile, "\t),\n");
fwrite($hFile, ");\n");
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
return fclose($hFile);
@@ -1482,26 +1702,6 @@ class Config
throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
}
}
protected static function ScanDictionariesDir()
{
$aResult = array();
// Populate automatically the list of dictionary files
$sDir = APPROOT.'/dictionaries';
if ($hDir = @opendir($sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (preg_match("/^([^\.]+\.)?dictionary\.itop\.(ui|core)\.php$/i", $sFile, $aMatches)) // Dictionary files named like [<Lang>.]dictionary.[core|ui].php are loaded automatically
{
$aResult[] = 'dictionaries/'.$sFile;
}
}
closedir($hDir);
}
return $aResult;
}
/**
* Helper function to initialize a configuration from the page arguments
@@ -1512,6 +1712,10 @@ class Config
{
$this->Set('app_root_url', $aParamValues['application_path']);
}
if (isset($aParamValues['graphviz_path']))
{
$this->Set('graphviz_path', $aParamValues['graphviz_path']);
}
if (isset($aParamValues['mode']) && isset($aParamValues['language']))
{
if (($aParamValues['mode'] == 'install') || $this->GetDefaultLanguage() == '')
@@ -1533,52 +1737,44 @@ class Config
$this->SetDBName($sDBName);
$this->SetDBSubname($aParamValues['db_prefix']);
}
if (!is_null($sModulesDir))
if (isset($aParamValues['selected_modules']))
{
if (isset($aParamValues['selected_modules']))
{
$aSelectedModules = explode(',', $aParamValues['selected_modules']);
}
else
{
$aSelectedModules = null;
}
$aSelectedModules = explode(',', $aParamValues['selected_modules']);
}
else
{
$aSelectedModules = null;
}
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
}
/**
* Helper function to rebuild the default configuration and the list of includes from a directory and a list of selected modules
* @param string $sModulesDir The relative path to the directory to scan for modules (typically the 'env-xxx' directory resulting from the compilation)
* @param array $aSelectedModules An array of selected modules' identifiers. If null all modules found will be considered as installed
* @throws Exception
*/
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
{
if (!is_null($sModulesDir))
{
// Initialize the arrays below with default values for the application...
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
$aAddOns = $oEmptyConfig->GetAddOns();
$aAppModules = $oEmptyConfig->GetAppModules();
$aDataModels = $oEmptyConfig->GetDataModels();
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
$aDictionaries = $oEmptyConfig->GetDictionaries();
// Merge the values with the ones provided by the modules
// Make sure when don't load the same file twice...
$aModules = ModuleDiscovery::GetAvailableModules(array(APPROOT.$sModulesDir));
foreach($aModules as $sModuleId => $aModuleInfo)
foreach ($aModules as $sModuleId => $aModuleInfo)
{
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
list ($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules))
{
if (isset($aModuleInfo['datamodel']))
{
$aDataModels = array_unique(array_merge($aDataModels, $aModuleInfo['datamodel']));
}
if (isset($aModuleInfo['webservice']))
{
$aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aModuleInfo['webservice']));
}
if (isset($aModuleInfo['dictionary']))
{
$aDictionaries = array_unique(array_merge($aDictionaries, $aModuleInfo['dictionary']));
}
if (isset($aModuleInfo['settings']))
{
list($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
foreach($aModuleInfo['settings'] as $sProperty => $value)
list ($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
foreach ($aModuleInfo['settings'] as $sProperty => $value)
{
if ($bPreserveModuleSettings && isset($this->m_aModuleSettings[$sName][$sProperty]))
if (isset($this->m_aModuleSettings[$sName][$sProperty]))
{
// Do nothing keep the original value
}
@@ -1599,16 +1795,12 @@ class Config
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
}
$aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig');
$aCallSpec = array($sModuleInstallerClass,'BeforeWritingConfig');
call_user_func_array($aCallSpec, array($this));
}
}
}
$this->SetAddOns($aAddOns);
$this->SetAppModules($aAppModules);
$this->SetDataModels($aDataModels);
$this->SetWebServiceCategories($aWebServiceCategories);
$this->SetDictionaries($aDictionaries);
}
}
@@ -1627,28 +1819,25 @@ class Config
}
/**
* Quick an dirty way to clone a config file into another environment
* Obsolete: kept only for backward compatibility of the Toolkit
* Quick and dirty way to clone a config file into another environment
*/
public function ChangeModulesPath($sSourceEnv, $sTargetEnv)
{
$sSearchPrefix = 'env-'.$sSourceEnv.'/';
$sNewPrefix = 'env-'.$sTargetEnv.'/';
self::ChangePrefix($this->m_aDataModels, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aWebServiceCategories, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aDictionaries, $sSearchPrefix, $sNewPrefix);
// Now does nothing since the includes are built into the environment itself
}
/**
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
* @param mixed $value The value to export
* @param string $sIdentation The string to use to indent the text
* @param string $sIndentation The string to use to indent the text
* @param bool $bForceIndentation Forces the identation (enven if it breaks/changes an eval, for example to ouput a value inside a comment)
* @return string The indented export string
*/
protected static function PrettyVarExport($value, $sIdentation, $bForceIndentation = false)
protected static function PrettyVarExport($value, $sIndentation, $bForceIndentation = false)
{
$sExport = var_export($value, true);
$sNiceExport = trim(preg_replace("/^/m", "\t\t\t", $sExport));
$sNiceExport = str_replace(array("\r\n", "\n", "\r"), "\n".$sIndentation, trim($sExport));
if (!$bForceIndentation)
{
eval('$aImported='.$sNiceExport.';');

View File

@@ -0,0 +1,75 @@
<?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/>
/**
* Simple helper class for keeping track of the context inside the call stack
*
* To check (anywhere in the code) if a particular context tag is present
* in the call stack simply do:
*
* if (ContextTag::Check(<the_tag>)) ...
*
* For example to know if the code is being executed in the context of a portal do:
*
* if (ContextTag::Check('GUI:Portal'))
*
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ContextTag
{
protected static $aStack;
/**
* Store a context tag on the stack
* @param string $sTag
*/
public function __construct($sTag)
{
static::$aStack[] = $sTag;
}
/**
* Cleanup the context stack
*/
public function __destruct()
{
array_pop(static::$aStack);
}
/**
* Check if a given tag is present in the stack
* @param string $sTag
* @return bool
*/
public static function Check($sTag)
{
return in_array($sTag, static::$aStack);
}
/**
* Get the whole stack as an array
* @return hash
*/
public static function GetStack()
{
return static::$aStack;
}
}

View File

@@ -0,0 +1,381 @@
<?php
// Copyright (C) 2015-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/>
/**
* Bulk export: CSV export
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class CSVBulkExport extends TabularBulkExport
{
public function DisplayUsage(Page $oP)
{
$oP->p(" * csv format options:");
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tseparator: (optional) character to be used as the separator (default is ',').");
$oP->p(" *\tcharset: (optional) character set for encoding the result (default is 'UTF-8').");
$oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"').");
$oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format used in the user interface). e.g. 'Y-m-d H:i:s'");
}
public function ReadParameters()
{
parent::ReadParameters();
$this->aStatusInfo['separator'] = utils::ReadParam('separator', ',', true, 'raw_data');
if (strtolower($this->aStatusInfo['separator']) == 'tab')
{
$this->aStatusInfo['separator'] = "\t";
}
else if (strtolower($this->aStatusInfo['separator']) == 'other')
{
$this->aStatusInfo['separator'] = utils::ReadParam('other-separator', ',', true, 'raw_data');
}
$this->aStatusInfo['text_qualifier'] = utils::ReadParam('text-qualifier', '"', true, 'raw_data');
if (strtolower($this->aStatusInfo['text_qualifier']) == 'other')
{
$this->aStatusInfo['text_qualifier'] = utils::ReadParam('other-text-qualifier', '"', true, 'raw_data');
}
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$sDateFormatRadio = utils::ReadParam('csv_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;
case 'custom':
// 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');
}
}
protected function SuggestField($sClass, $sAttCode)
{
switch($sAttCode)
{
case 'id': // replace 'id' by 'friendlyname'
$sAttCode = 'friendlyname';
break;
default:
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey)
{
$sAttCode .= '_friendlyname';
}
}
return parent::SuggestField($sClass, $sAttCode);
}
public function EnumFormParts()
{
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize', 'formatted_text') ,'interactive_fields_csv' => array('interactive_fields_csv')));
}
public function DisplayFormPart(WebPage $oP, $sPartId)
{
switch($sPartId)
{
case 'interactive_fields_csv':
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_csv');
break;
case 'csv_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:CSVOptions').'</legend>');
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');
$sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data');
$aSep = array(
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
'tab' => Dict::S('UI:CSVImport:SeparatorTab+'),
);
$sOtherSeparator = '';
if (!array_key_exists($sRawSeparator, $aSep))
{
$sOtherSeparator = $sRawSeparator;
$sRawSeparator = 'other';
}
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.htmlentities($sOtherSeparator, ENT_QUOTES, 'UTF-8').'"/>';
foreach($aSep as $sVal => $sLabel)
{
$sChecked = ($sVal == $sRawSeparator) ? 'checked' : '';
$oP->add('<input type="radio" name="separator" value="'.htmlentities($sVal, ENT_QUOTES, 'UTF-8').'" '.$sChecked.'/>&nbsp;'.$sLabel.'<br/>');
}
$oP->add('</td><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('UI:CSVImport:TextQualifierCharacter').'</h3>');
$sRawQualifier = utils::ReadParam('text-qualifier', '"', true, 'raw_data');
$aQualifiers = array(
'"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'),
'\'' => Dict::S('UI:CSVImport:QualifierSimpleQuote+'),
);
$sOtherQualifier = '';
if (!array_key_exists($sRawQualifier, $aQualifiers))
{
$sOtherQualifier = $sRawQualifier;
$sRawQualifier = 'other';
}
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.htmlentities($sOtherQualifier, ENT_QUOTES, 'UTF-8').'"/>';
foreach($aQualifiers as $sVal => $sLabel)
{
$sChecked = ($sVal == $sRawQualifier) ? 'checked' : '';
$oP->add('<input type="radio" name="text-qualifier" value="'.htmlentities($sVal, ENT_QUOTES, 'UTF-8').'" '.$sChecked.'/>&nbsp;'.$sLabel.'<br/>');
}
$sChecked = (utils::ReadParam('no_localize', 0) == 1) ? ' checked ' : '';
$oP->add('</td><td style="vertical-align:top">');
$oP->add('<h3>'.Dict::S('Core:BulkExport:CSVLocalization').'</h3>');
$oP->add('<input type="checkbox" id="csv_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="csv_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label>');
$oP->add('<br/>');
$oP->add('<br/>');
$oP->add(Dict::S('UI:CSVImport:Encoding').': <select name="charset" style="font-family:Arial,Helvetica,Sans-serif">'); // IE 8 has some troubles if the font is different
$aPossibleEncodings = utils::GetPossibleEncodings(MetaModel::GetConfig()->GetCSVImportCharsets());
$sDefaultEncoding = MetaModel::GetConfig()->Get('csv_file_default_charset');
foreach($aPossibleEncodings as $sIconvCode => $sDisplayName )
{
$sSelected = '';
if ($sIconvCode == $sDefaultEncoding)
{
$sSelected = ' selected';
}
$oP->add('<option value="'.$sIconvCode.'"'.$sSelected.'>'.$sDisplayName.'</option>');
}
$oP->add('</select>');
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td><td style="vertical-align:top">');
$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('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
$oP->add('<input type="radio" id="csv_date_time_format_default" name="csv_date_format_radio" value="default"'.$sDefaultChecked.'><label for="csv_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="csv_date_time_format_custom" name="csv_date_format_radio" value="custom"'.$sCustomChecked.'><label for="csv_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
$('#csv_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
$('#csv_date_time_format_default').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
$('#csv_date_time_format_custom').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
$('#csv_custom_date_time_format').on('click', function() { $('#csv_date_time_format_custom').prop('checked', true); FormatDatesInPreview('csv', 'csv'); }).on('keyup', function() { FormatDatesInPreview('csv', 'csv'); });
EOF
);
break;
default:
return parent:: DisplayFormPart($oP, $sPartId);
}
}
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode != 'id')
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
}
}
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
}
protected function GetValue($oObj, $sAttCode)
{
switch($sAttCode)
{
case 'id':
$sRet = $oObj->GetKey();
break;
default:
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
}
return $sRet;
}
public function GetHeader()
{
$oSet = new DBObjectSet($this->oSearch);
$this->aStatusInfo['status'] = 'running';
$this->aStatusInfo['position'] = 0;
$this->aStatusInfo['total'] = $oSet->Count();
$aData = array();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$aData[] = $aFieldSpec['sColLabel'];
}
$sFrom = array("\r\n", $this->aStatusInfo['text_qualifier']);
$sTo = array("\n", $this->aStatusInfo['text_qualifier'].$this->aStatusInfo['text_qualifier']);
foreach($aData as $idx => $sData)
{
// Escape and encode (if needed) the headers
$sEscaped = str_replace($sFrom, $sTo, (string)$sData);
$aData[$idx] = $this->aStatusInfo['text_qualifier'].$sEscaped.$this->aStatusInfo['text_qualifier'];
if ($this->aStatusInfo['charset'] != 'UTF-8')
{
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
$aData[$idx] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $aData[$idx]);
}
}
$sData = implode($this->aStatusInfo['separator'], $aData)."\n";
return $sData;
}
public function GetNextChunk(&$aStatus)
{
$sRetCode = 'run';
$iPercentage = 0;
$oSet = new DBObjectSet($this->oSearch);
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
$this->OptimizeColumnLoad($oSet);
$iCount = 0;
$sData = '';
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
$oPrevDateTimeFormat = AttributeDateTime::GetFormat();
$oPrevDateFormat = AttributeDate::GetFormat();
if ($sExportDateTimeFormat !== (string)$oPrevDateTimeFormat)
{
// Change date & time formats
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
AttributeDateTime::SetFormat($oDateTimeFormat);
AttributeDate::SetFormat($oDateFormat);
}
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
$aData = array();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
$sField = '';
$oObj = $aRow[$sAlias];
if ($oObj != null)
{
switch($sAttCode)
{
case 'id':
$sField = $oObj->GetKey();
break;
default:
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
}
}
if ($this->aStatusInfo['charset'] != 'UTF-8')
{
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
$aData[] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $sField);
}
else
{
$aData[] = $sField;
}
}
$sData .= implode($this->aStatusInfo['separator'], $aData)."\n";
$iCount++;
}
// Restore original date & time formats
AttributeDateTime::SetFormat($oPrevDateTimeFormat);
AttributeDate::SetFormat($oPrevDateFormat);
set_time_limit($iPreviousTimeLimit);
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{
$iPercentage = 100;
}
else
{
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
}
if ($iCount < $this->iChunkSize)
{
$sRetCode = 'done';
}
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
return $sData;
}
public function GetSupportedFormats()
{
return array('csv' => Dict::S('Core:BulkExport:CSVFormat'));
}
public function GetMimeType()
{
return 'text/csv';
}
public function GetFileExtension()
{
return 'csv';
}
public function GetCharacterSet()
{
return $this->aStatusInfo['charset'];
}
}

View File

@@ -54,12 +54,14 @@ class CSVParser
private $m_sCSVData;
private $m_sSep;
private $m_sTextQualifier;
private $m_iTimeLimitPerRow;
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"')
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"', $iTimeLimitPerRow = null)
{
$this->m_sCSVData = str_replace("\r\n", "\n", $sTxt);
$this->m_sSep = $sSep;
$this->m_sTextQualifier = $sTextQualifier;
$this->m_iTimeLimitPerRow = $iTimeLimitPerRow;
}
protected $m_sCurrCell = '';
@@ -129,6 +131,12 @@ class CSVParser
// blank line, skip silently
}
$this->m_aCurrRow = array();
// More time for the next row
if ($this->m_iTimeLimitPerRow !== null)
{
set_time_limit($this->m_iTimeLimitPerRow);
}
}
protected function __AddCellTrimmed($c = null, $aFieldMap = null)
{
@@ -181,6 +189,13 @@ class CSVParser
$iDataLength = strlen($this->m_sCSVData);
$iState = stSTARTING;
$iTimeLimit = null;
if ($this->m_iTimeLimitPerRow !== null)
{
// Give some time for the first row
$iTimeLimit = ini_get('max_execution_time');
set_time_limit($this->m_iTimeLimitPerRow);
}
for($i = 0; $i <= $iDataLength ; $i++)
{
if ($i == $iDataLength)
@@ -237,6 +252,11 @@ class CSVParser
$iLineCount = count($this->m_aDataSet);
if (($iMax > 0) && ($iLineCount >= $iMax)) break;
}
if ($iTimeLimit !== null)
{
// Restore the previous time limit
set_time_limit($iTimeLimit);
}
return $this->m_aDataSet;
}

View File

@@ -0,0 +1,140 @@
<?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/>
use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\FormManager;
/**
* Base class to implement a handler for AttributeCustomFields
*
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class CustomFieldsHandler
{
protected $sAttCode;
protected $aValues;
protected $oForm;
/**
* This constructor's prototype must be frozen.
* Any specific behavior must be implemented in BuildForm()
*
* @param $sAttCode
*/
final public function __construct($sAttCode)
{
$this->sAttCode = $sAttCode;
$this->aValues = null;
}
abstract public function BuildForm(DBObject $oHostObject, $sFormId);
/**
*
* @return \Combodo\iTop\Form\Form
*/
public function GetForm()
{
return $this->oForm;
}
public function SetCurrentValues($aValues)
{
$this->aValues = $aValues;
}
static public function GetPrerequisiteAttributes($sClass = null)
{
return array();
}
/**
* List the available verbs for 'GetForTemplate'
*/
static public function EnumTemplateVerbs()
{
return array();
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
* @param $aValues array The current values
* @param $sVerb string The verb specifying the representation of the value
* @param $bLocalize bool Whether or not to localize the value
* @return string
*/
abstract public function GetForTemplate($aValues, $sVerb, $bLocalize = true);
/**
* @param $aValues
* @param bool|true $bLocalize
* @return mixed
*/
abstract public function GetAsHTML($aValues, $bLocalize = true);
/**
* @param $aValues
* @param bool|true $bLocalize
* @return mixed
*/
abstract public function GetAsXML($aValues, $bLocalize = true);
/**
* @param $aValues
* @param string $sSeparator
* @param string $sTextQualifier
* @param bool|true $bLocalize
* @return mixed
*/
abstract public function GetAsCSV($aValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true);
/**
* @param DBObject $oHostObject
* @return array Associative array id => value
*/
abstract public function ReadValues(DBObject $oHostObject);
/**
* Record the data (currently in the processing of recording the host object)
* It is assumed that the data has been checked prior to calling Write()
* @param DBObject $oHostObject
* @param array Associative array id => value
*/
abstract public function WriteValues(DBObject $oHostObject, $aValues);
/**
* Cleanup data upon object deletion (object id still available here)
* @param DBObject $oHostObject
*/
abstract public function DeleteValues(DBObject $oHostObject);
/**
* @param $aValuesA
* @param $aValuesB
* @return bool
*/
abstract public function CompareValues($aValuesA, $aValuesB);
/**
* String representation of the value, must depend solely on the semantics
* @return string
*/
abstract public function GetValueFingerprint();
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -21,7 +21,7 @@
* data generator
* helps the consultants in creating dummy data sets, for various test purposes (validation, usability, scalability)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -267,7 +267,7 @@ class cmdbDataGenerator
function GenerateKey($sClass, $aFilterCriteria)
{
$retKey = null;
$oFilter = new CMDBSearchFilter($sClass);
$oFilter = new DBObjectSearch($sClass);
foreach($aFilterCriteria as $sFilterCode => $filterValue)
{
$oFilter->AddCondition($sFilterCode, $filterValue, '=');
@@ -346,7 +346,7 @@ class cmdbDataGenerator
*/
protected function OrganizationExists($sCode)
{
$oFilter = new CMDBSearchFilter('bizOrganization');
$oFilter = new DBObjectSearch('Organization');
$oFilter->AddCondition('code', $sCode, '=');
$oSet = new CMDBObjectSet($oFilter);
return ($oSet->Count() > 0);
@@ -361,7 +361,7 @@ class cmdbDataGenerator
protected function GetOrganization($sId)
{
$oOrg = null;
$oFilter = new CMDBSearchFilter('bizOrganization');
$oFilter = new DBObjectSearch('Organization');
$oFilter->AddCondition('id', $sId, '=');
$oSet = new CMDBObjectSet($oFilter);
if ($oSet->Count() > 0)

3
core/datamodel.core.xml Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design>
</itop_design>

View File

@@ -0,0 +1,422 @@
<?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/>
/**
* Helper class to generate Date & Time formatting strings in the various conventions
* from the PHP DateTime::createFromFormat convention.
*
* Example:
*
* $oFormat = new DateTimeFormat('m/d/Y H:i');
* $oFormat->ToExcel();
* >> 'MM/dd/YYYY HH:mm'
*
* @author Denis Flaven <denis.flaven@combodo.com>
*
*/
class DateTimeFormat
{
protected $sPHPFormat;
/**
* Constructs the DateTimeFormat object
* @param string $sPHPFormat A format string using the PHP 'DateTime::createFromFormat' convention
*/
public function __construct($sPHPFormat)
{
$this->sPHPFormat = (string)$sPHPFormat;
}
/**
* @return string
*/
public function __toString()
{
return $this->sPHPFormat;
}
/**
* Return the mapping table for converting between various conventions for date/time formats
*/
protected static function GetFormatMapping()
{
return array(
// Days
'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'excel' => 'dd', 'moment' => 'DD'), // Day of the month: 2 digits (with leading zero)
'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'excel' => 'd', 'moment' => 'D'), // Day of the month: 1 or 2 digits (without leading zero)
// Months
'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'excel' => 'MM', 'moment' => 'MM' ), // Month on 2 digits i.e. 01-12
'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'excel' => 'm', 'moment' => 'M'), // Month on 1 or 2 digits 1-12
// Years
'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'excel' => 'YYYY', 'moment' => 'YYYY'), // Year on 4 digits
'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'excel' => 'YY', 'moment' => 'YY'), // Year on 2 digits
// Hours
'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'excel' => 'HH', 'moment' => 'HH'), // Hour 00..23
'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'excel' => 'hh', 'moment' => 'hh'), // Hour 01..12
'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'excel' => 'H', 'moment' => 'H'), // Hour 0..23
'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'excel' => 'h', 'moment' => 'h'), // Hour 1..12
'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'excel' => 'am/pm', 'moment' => 'a'),
'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'excel' => 'AM/PM', 'moment' => 'A'),
// Minutes
'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'excel' => 'mm', 'moment' => 'mm'),
// Seconds
's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'excel' => 'ss', 'moment' => 'ss'),
);
}
/**
* Transform the PHP format into the specified format, taking care of escaping the litteral characters
* using the supplied escaping expression
* @param string $sOutputFormatCode THe target format code: regexpr|datepicker|excel|moment
* @param string $sEscapePattern The replacement string for escaping characters in the output string. %s is the source char.
* @param string $bEscapeAll True to systematically escape all litteral characters
* @param array $sSpecialChars A string containing the only characters to escape in the output
* @return string The string in the requested format
*/
protected function Transform($sOutputFormatCode, $sEscapePattern, $bEscapeAll = false, $sSpecialChars = '')
{
$aMappings = static::GetFormatMapping();
$sResult = '';
$bEscaping = false;
for($i=0; $i < strlen($this->sPHPFormat); $i++)
{
if (($this->sPHPFormat[$i] == '\\'))
{
$bEscaping = true;
continue;
}
if ($bEscaping)
{
if (($sSpecialChars === '') || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
{
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
}
else
{
$sResult .= $this->sPHPFormat[$i];
}
$bEscaping = false;
}
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
{
// Not a litteral value, must be replaced by its regular expression pattern
$sResult .= $aMappings[$this->sPHPFormat[$i]][$sOutputFormatCode];
}
else
{
if ($bEscapeAll || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
{
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
}
else
{
// Normal char with no special meaning, no need to escape it
$sResult .= $this->sPHPFormat[$i];
}
}
}
return $sResult;
}
/**
* Format a date into the supplied format string
* @param mixed $date An int, string, DateTime object or null !!
* @throws Exception
* @return string The formatted date
*/
public function Format($date)
{
if ($date == null)
{
$sDate = '';
}
else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00'))
{
$sDate = '';
}
else if ($date instanceof DateTime)
{
// Parameter is a DateTime
$sDate = $date->format($this->sPHPFormat);
}
else if (is_int($date))
{
// Parameter is a Unix timestamp
$oDate = new DateTime();
$oDate->setTimestamp($date);
$sDate = $oDate->format($this->sPHPFormat);
}
else if (is_string($date))
{
$oDate = new DateTime($date);
$sDate = $oDate->format($this->sPHPFormat);
}
else
{
throw new Exception(__CLASS__."::Format: Unexpected date value: ".print_r($date, true));
}
return $sDate;
}
/**
* Parse a date in the supplied format and return the date as a string in the internal format
* @param string $sDate The string to parse
* @param string $sFormat The format, in PHP createFromFormat convention
* @throws Exception
* @return DateTime|null
*/
public function Parse($sDate)
{
if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
{
return null;
}
else
{
$sFormat = preg_replace('/\\?/', '', $this->sPHPFormat); // replace escaped characters by a wildcard for parsing
$oDate = DateTime::createFromFormat($this->sPHPFormat, $sDate);
if ($oDate === false)
{
throw new Exception(__CLASS__."::Parse: Unable to parse the date: '$sDate' using the format: '{$this->sPHPFormat}'");
}
return $oDate;
}
}
/**
* Get the date or datetime format string in the jQuery UI date picker format
* @return string The format string using the date picker convention
*/
public function ToDatePicker()
{
return $this->Transform('datepicker', "'%s'");
}
/**
* Get a date or datetime format string in the Excel format
* @return string The format string using the Excel convention
*/
public function ToExcel()
{
return $this->Transform('excel', "%s");
}
/**
* Get a date or datetime format string in the moment.js format
* @return string The format string using the moment.js convention
*/
public function ToMomentJS()
{
return $this->Transform('moment', "[%s]", true /* escape all */);
}
public static function GetJSSQLToCustomFormat()
{
$aPHPToMoment = array();
foreach(self::GetFormatMapping() as $sPHPCode => $aMapping)
{
$aPHPToMoment[$sPHPCode] = $aMapping['moment'];
}
$sJSMapping = json_encode($aPHPToMoment);
$sFunction =
<<<EOF
function PHPDateTimeFormatToSubFormat(sPHPFormat, sPlaceholders)
{
var iMax = 0;
var iMin = 999;
var bEscaping = false;
for(var i=0; i<sPHPFormat.length; i++)
{
var c = sPHPFormat[i];
if (c == '\\\\')
{
bEscaping = true;
continue;
}
if (bEscaping)
{
bEscaping = false;
continue;
}
else
{
if (sPlaceholders.search(c) != -1)
{
iMax = Math.max(iMax, i);
iMin = Math.min(iMin, i);
}
}
}
return sPHPFormat.substr(iMin, iMax - iMin + 1);
}
function PHPDateTimeFormatToMomentFormat(sPHPFormat)
{
var aFormatMapping = $sJSMapping;
var sMomentFormat = '';
var bEscaping = false;
for(var i=0; i<sPHPFormat.length; i++)
{
var c = sPHPFormat[i];
if (c == '\\\\')
{
bEscaping = true;
continue;
}
if (bEscaping)
{
sMomentFormat += '['+c+']';
bEscaping = false;
}
else
{
if (aFormatMapping[c] !== undefined)
{
sMomentFormat += aFormatMapping[c];
}
else
{
sMomentFormat += '['+c+']';
}
}
}
return sMomentFormat;
}
function DateFormatFromPHP(sSQLDate, sPHPFormat)
{
if (sSQLDate === '') return '';
var sPHPDateFormat = PHPDateTimeFormatToSubFormat(sPHPFormat, 'Yydjmn');
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPDateFormat);
return moment(sSQLDate).format(sMomentFormat);
}
function DateTimeFormatFromPHP(sSQLDate, sPHPFormat)
{
if (sSQLDate === '') return '';
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPFormat);
return moment(sSQLDate).format(sMomentFormat);
}
EOF
;
return $sFunction;
}
/**
* Get a placeholder text for a date or datetime format string
* @return string The placeholder text (localized)
*/
public function ToPlaceholder()
{
$aMappings = static::GetFormatMapping();
$sResult = '';
$bEscaping = false;
for($i=0; $i < strlen($this->sPHPFormat); $i++)
{
if (($this->sPHPFormat[$i] == '\\'))
{
$bEscaping = true;
continue;
}
if ($bEscaping)
{
$sResult .= $this->sPHPFormat[$i]; // No need to escape characters in the placeholder
$bEscaping = false;
}
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
{
// Not a litteral value, must be replaced by Dict equivalent
$sResult .= Dict::S('Core:DateTime:Placeholder_'.$this->sPHPFormat[$i]);
}
else
{
// Normal char with no special meaning
$sResult .= $this->sPHPFormat[$i];
}
}
return $sResult;
}
/**
* Produces a subformat (Date or Time) by extracting the part of the whole DateTime format containing only the given placeholders
* @return string
*/
protected function ToSubFormat($aPlaceholders)
{
$iStart = 999;
$iEnd = 0;
foreach($aPlaceholders as $sChar)
{
$iPos = strpos($this->sPHPFormat, $sChar);
if ($iPos !== false)
{
if (($iPos > 0) && ($this->sPHPFormat[$iPos-1] == '\\'))
{
// The placeholder is actually escaped, it's a litteral character, ignore it
continue;
}
$iStart = min($iStart, $iPos);
$iEnd = max($iEnd, $iPos);
}
}
$sFormat = substr($this->sPHPFormat, $iStart, $iEnd - $iStart + 1);
return $sFormat;
}
/**
* Produces the Date format string by extracting only the date part of the date and time format string
* @return string
*/
public function ToDateFormat()
{
return $this->ToSubFormat(array('Y', 'y', 'd', 'j', 'm', 'n'));
}
/**
* Produces the Time format string by extracting only the time part of the date and time format string
* @return string
*/
public function ToTimeFormat()
{
return $this->ToSubFormat(array('H', 'h', 'G', 'g', 'i', 's', 'a', 'A'));
}
/**
* 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!)
* @return string The regular expression in PCRE syntax
*/
public function ToRegExpr()
{
return '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]/:').'$';
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

835
core/dbsearch.class.php Normal file
View File

@@ -0,0 +1,835 @@
<?php
// Copyright (C) 2015-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/>
require_once('dbobjectsearch.class.php');
require_once('dbunionsearch.class.php');
/**
* An object search
*
* Note: in the ancient times of iTop, a search was named after DBObjectSearch.
* When the UNION has been introduced, it has been decided to:
* - declare a hierarchy of search classes, with two leafs :
* - one class to cope with a single query (A JOIN B... WHERE...)
* - and the other to cope with several queries (query1 UNION query2)
* - in order to preserve forward/backward compatibility of the existing modules
* - keep the name of DBObjectSearch even if it a little bit confusing
* - do not provide a type-hint for function parameters defined in the modules
* - leave the statements DBObjectSearch::FromOQL in the modules, though DBSearch is more relevant
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
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()
{
}
/**
* Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects)
**/
public function DeepClone()
{
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;}
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)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
}
public function GetModifierProperties($sPluginClass)
{
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
{
return $this->m_aModifierProperties[$sPluginClass];
}
else
{
return array();
}
}
abstract public function GetClassName($sAlias);
abstract public function GetClass();
abstract public function GetClassAlias();
/**
* Change the class (only subclasses are supported as of now, because the conditions must fit the new class)
* Defaults to the first selected class (most of the time it is also the first joined class
*/
abstract public function ChangeClass($sNewClass, $sAlias = null);
abstract public function GetSelectedClasses();
/**
* @param array $aSelectedClasses array of aliases
* @throws CoreException
*/
abstract public function SetSelectedClasses($aSelectedClasses);
abstract public function IsAny();
public function Describe(){return 'deprecated - use ToOQL() instead';}
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo){return 'deprecated - use ToOQL() instead';}
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode){return 'deprecated - use ToOQL() instead';}
public function DescribeConditionRelTo($aRelInfo){return 'deprecated - use ToOQL() instead';}
public function DescribeConditions(){return 'deprecated - use ToOQL() instead';}
public function __DescribeHTML(){return 'deprecated - use ToOQL() instead';}
abstract public function ResetCondition();
abstract public function MergeConditionExpression($oExpression);
abstract public function AddConditionExpression($oExpression);
abstract public function AddNameCondition($sName);
abstract public function AddCondition($sFilterCode, $value, $sOpCode = null);
/**
* Specify a condition on external keys or link sets
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
* Example: infra_list->ci_id->location_id->country
* @param value The value to match (can be an array => IN(val1, val2...)
* @return void
*/
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);
abstract public function Intersect(DBSearch $oFilter);
/**
*
* @param DBSearch $oFilter
* @param integer $iDirection
* @param string $sExtKeyAttCode
* @param integer $iOperatorCode
* @return DBSearch
*/
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
{
$oSourceFilter = $this->DeepClone();
$oRet = null;
if ($oFilter instanceof DBUnionSearch)
{
$aSearches = array();
foreach ($oFilter->GetSearches() as $oSearch)
{
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode);
}
$oRet = new DBUnionSearch($aSearches);
}
else
{
if ($iDirection === static::JOIN_POINTING_TO)
{
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
}
else
{
if ($iOperatorCode !== TREE_OPERATOR_EQUALS)
{
throw new Exception('Only TREE_OPERATOR_EQUALS operator code is supported yet for AddCondition_ReferencedBy.');
}
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode);
}
$oRet = $oSourceFilter;
}
return $oRet;
}
abstract public function SetInternalParams($aParams);
abstract public function GetInternalParams();
abstract public function GetQueryParams($bExcludeMagicParams = true);
abstract public function ListConstantFields();
/**
* Turn the parameters (:xxx) into scalar values in order to easily
* serialize a search
*/
abstract public function ApplyParameters($aArgs);
public function serialize($bDevelopParams = false, $aContextParams = null)
{
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
return base64_encode(serialize(array($sOql, $this->GetInternalParams(), $this->m_aModifierProperties)));
}
static public function unserialize($sValue)
{
$aData = unserialize(base64_decode($sValue));
$sOql = $aData[0];
$aParams = $aData[1];
// We've tried to use gzcompress/gzuncompress, but for some specific queries
// it was not working at all (See Trac #193)
// gzuncompress was issuing a warning "data error" and the return object was null
$oRetFilter = self::FromOQL($sOql, $aParams);
$oRetFilter->m_aModifierProperties = $aData[2];
return $oRetFilter;
}
/**
* Create a new DBObjectSearch from $oSearch with a new alias $sAlias
*
* Note : This has not be tested with UNION queries.
*
* @param DBSearch $oSearch
* @param string $sAlias
* @return DBObjectSearch
*/
static public function CloneWithAlias(DBSearch $oSearch, $sAlias)
{
$oSearchWithAlias = new DBObjectSearch($oSearch->GetClass(), $sAlias);
$oSearchWithAlias = $oSearchWithAlias->Intersect($oSearch);
return $oSearchWithAlias;
}
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null);
static protected $m_aOQLQueries = array();
// Do not filter out depending on user rights
// In particular when we are currently in the process of evaluating the user rights...
static public function FromOQL_AllData($sQuery, $aParams = null)
{
$oRes = self::FromOQL($sQuery, $aParams);
$oRes->AllowAllData();
return $oRes;
}
/**
* @param string $sQuery
* @param array $aParams
* @return DBSearch
* @throws OQLException
*/
static public function FromOQL($sQuery, $aParams = null)
{
if (empty($sQuery)) return null;
// Query caching
$sQueryId = md5($sQuery);
$bOQLCacheEnabled = true;
if ($bOQLCacheEnabled)
{
if (array_key_exists($sQueryId, self::$m_aOQLQueries))
{
// hit!
$oResultFilter = self::$m_aOQLQueries[$sQueryId]->DeepClone();
}
elseif (self::$m_bUseAPCCache)
{
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
$sAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-dbsearch-cache-'.$sQueryId;
$oKPI = new ExecutionKPI();
$result = apc_fetch($sAPCCacheId);
$oKPI->ComputeStats('Search APC (fetch)', $sQuery);
if (is_object($result))
{
$oResultFilter = $result;
self::$m_aOQLQueries[$sQueryId] = $oResultFilter->DeepClone();
}
}
}
if (!isset($oResultFilter))
{
$oKPI = new ExecutionKPI();
$oOql = new OqlInterpreter($sQuery);
$oOqlQuery = $oOql->ParseQuery();
$oMetaModel = new ModelReflectionRuntime();
$oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue
$oResultFilter = $oOqlQuery->ToDBSearch($sQuery);
$oKPI->ComputeStats('Parse OQL', $sQuery);
if ($bOQLCacheEnabled)
{
self::$m_aOQLQueries[$sQueryId] = $oResultFilter->DeepClone();
if (self::$m_bUseAPCCache)
{
$oKPI = new ExecutionKPI();
apc_store($sAPCCacheId, $oResultFilter, self::$m_iQueryCacheTTL);
$oKPI->ComputeStats('Search APC (store)', $sQueryId);
}
}
}
if (!is_null($aParams))
{
$oResultFilter->SetInternalParams($aParams);
}
return $oResultFilter;
}
// Alternative to object mapping: the data are transfered directly into an array
// This is 10 times faster than creating a set of objects, and makes sense when optimization is required
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
*/
public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array())
{
$sSQL = $this->MakeSelectQuery($aOrderBy, $aArgs);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return;
if (count($aColumns) == 0)
{
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
// Add the standard id (as first column)
array_unshift($aColumns, 'id');
}
$aQueryCols = CMDBSource::GetColumns($resQuery);
$sClassAlias = $this->GetClassAlias();
$aColMap = array();
foreach ($aColumns as $sAttCode)
{
$sColName = $sClassAlias.$sAttCode;
if (in_array($sColName, $aQueryCols))
{
$aColMap[$sAttCode] = $sColName;
}
}
$aRes = array();
while ($aRow = CMDBSource::FetchArray($resQuery))
{
$aMappedRow = array();
foreach ($aColMap as $sAttCode => $sColName)
{
$aMappedRow[$sAttCode] = $aRow[$sColName];
}
$aRes[] = $aMappedRow;
}
CMDBSource::FreeResult($resQuery);
return $aRes;
}
////////////////////////////////////////////////////////////////////////////
//
// Construction of the SQL queries
//
////////////////////////////////////////////////////////////////////////////
protected static $m_aQueryStructCache = array();
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false)
{
if ($bExcludeNullValues)
{
// Null values are not handled (though external keys set to 0 are allowed)
$oQueryFilter = $this->DeepClone();
foreach ($aGroupByExpr as $oGroupByExp)
{
$oNull = new FunctionExpression('ISNULL', array($oGroupByExp));
$oNotNull = new BinaryExpression($oNull, '!=', new TrueExpression());
$oQueryFilter->AddConditionExpression($oNotNull);
}
}
else
{
$oQueryFilter = $this;
}
$aAttToLoad = array();
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
try
{
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL);
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $this->ToOQL());
throw $e;
}
$this->AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sRes);
return $sRes;
}
/**
* @param array|hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param array $aArgs
* @param null $aAttToLoad
* @param null $aExtendedDataSpec
* @param int $iLimitCount
* @param int $iLimitStart
* @param bool $bGetCount
* @return string
* @throws CoreException
* @throws Exception
* @throws MissingQueryArgument
*/
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
{
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
//
$sClass = $this->GetClass();
$sClassAlias = $this->GetClassAlias();
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
$iDotPos = strpos($sFieldAlias, '.');
if ($iDotPos === false)
{
$sAttClass = $sClass;
$sAttClassAlias = $sClassAlias;
$sAttCode = $sFieldAlias;
}
else
{
$sAttClassAlias = substr($sFieldAlias, 0, $iDotPos);
$sAttClass = $this->GetClassName($sAttClassAlias);
$sAttCode = substr($sFieldAlias, $iDotPos + 1);
}
if ($sAttCode != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sAttCode, MetaModel::GetAttributesList($sAttClass));
$oAttDef = MetaModel::GetAttributeDef($sAttClass, $sAttCode);
foreach($oAttDef->GetOrderBySQLExpressions($sAttClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sAttClassAlias.$sAttCode.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sAttClassAlias][$sAttCode]))
{
$aAttToLoad[$sAttClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sAttClass, $sAttCode);
}
}
$oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
if ($this->m_bNoContextParameters)
{
// Only internal parameters
$aScalarArgs = $this->GetInternalParams();
}
else
{
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
}
try
{
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
if ($sClassAlias == '_itop_')
{
IssueLog::Info('SQL Query (_itop_): '.$sRes);
}
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $this->ToOQL());
throw $e;
}
$this->AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sRes);
return $sRes;
}
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();
}
}
// Join to an additional table, if required...
//
if ($aExtendedDataSpec != null)
{
$sTableAlias = '_extended_data_';
$aExtendedFields = array();
foreach($aExtendedDataSpec['fields'] as $sColumn)
{
$sColRef = $oSearch->GetClassAlias().'_extdata_'.$sColumn;
$aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias);
}
$oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields);
$oSQLQuery->AddInnerJoin($oSQLQueryExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/);
}
return $oSQLQuery;
}
////////////////////////////////////////////////////////////////////////////
//
// Cache/Trace/Log queries
//
////////////////////////////////////////////////////////////////////////////
protected static $m_bDebugQuery = false;
protected static $m_aQueriesLog = array();
protected static $m_bQueryCacheEnabled = false;
protected static $m_bUseAPCCache = false;
protected static $m_iQueryCacheTTL = 3600;
protected static $m_bTraceQueries = false;
protected static $m_bIndentQueries = false;
protected static $m_bOptimizeQueries = false;
public static function StartDebugQuery()
{
$aBacktrace = debug_backtrace();
self::$m_bDebugQuery = true;
}
public static function StopDebugQuery()
{
self::$m_bDebugQuery = false;
}
public static function EnableQueryCache($bEnabled, $bUseAPC, $iTimeToLive = 3600)
{
self::$m_bQueryCacheEnabled = $bEnabled;
self::$m_bUseAPCCache = $bUseAPC;
self::$m_iQueryCacheTTL = $iTimeToLive;
}
public static function EnableQueryTrace($bEnabled)
{
self::$m_bTraceQueries = $bEnabled;
}
public static function EnableQueryIndentation($bEnabled)
{
self::$m_bIndentQueries = $bEnabled;
}
public static function EnableOptimizeQuery($bEnabled)
{
self::$m_bOptimizeQueries = $bEnabled;
}
protected function AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sSql)
{
if (self::$m_bTraceQueries)
{
$aQueryData = array(
'type' => 'select',
'filter' => $this,
'order_by' => $aOrderBy,
'args' => $aArgs,
'att_to_load' => $aAttToLoad,
'extended_data_spec' => $aExtendedDataSpec,
'limit_count' => $iLimitCount,
'limit_start' => $iLimitStart,
'is_count' => $bGetCount
);
$sOql = $this->ToOQL(true, $aArgs);
self::AddQueryTrace($aQueryData, $sOql, $sSql);
}
}
protected function AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sSql)
{
if (self::$m_bTraceQueries)
{
$aQueryData = array(
'type' => 'group_by',
'filter' => $this,
'args' => $aArgs,
'group_by_expr' => $aGroupByExpr
);
$sOql = $this->ToOQL(true, $aArgs);
self::AddQueryTrace($aQueryData, $sOql, $sSql);
}
}
protected static function AddQueryTrace($aQueryData, $sOql, $sSql)
{
if (self::$m_bTraceQueries)
{
$sQueryId = md5(serialize($aQueryData));
$sMySQLQueryId = md5($sSql);
if(!isset(self::$m_aQueriesLog[$sQueryId]))
{
self::$m_aQueriesLog[$sQueryId]['data'] = serialize($aQueryData);
self::$m_aQueriesLog[$sQueryId]['oql'] = $sOql;
self::$m_aQueriesLog[$sQueryId]['hits'] = 1;
}
else
{
self::$m_aQueriesLog[$sQueryId]['hits']++;
}
if(!isset(self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]))
{
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['sql'] = $sSql;
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count'] = 1;
$iTableCount = count(CMDBSource::ExplainQuery($sSql));
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['table_count'] = $iTableCount;
}
else
{
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count']++;
}
}
}
public static function RecordQueryTrace()
{
if (!self::$m_bTraceQueries) return;
$iOqlCount = count(self::$m_aQueriesLog);
$iSqlCount = 0;
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
{
$iSqlCount += $aOqlData['hits'];
}
$sHtml = "<h2>Stats on SELECT queries: OQL=$iOqlCount, SQL=$iSqlCount</h2>\n";
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
{
$sOql = $aOqlData['oql'];
$sHits = $aOqlData['hits'];
$sHtml .= "<p><b>$sHits</b> hits for OQL query: $sOql</p>\n";
$sHtml .= "<ul id=\"ClassesRelationships\" class=\"treeview\">\n";
foreach($aOqlData['queries'] as $aSqlData)
{
$sQuery = $aSqlData['sql'];
$sSqlHits = $aSqlData['count'];
$iTableCount = $aSqlData['table_count'];
$sHtml .= "<li><b>$sSqlHits</b> hits for SQL ($iTableCount tables): <pre style=\"font-size:60%\">$sQuery</pre></li>\n";
}
$sHtml .= "</ul>\n";
}
$sLogFile = 'queries.latest';
file_put_contents(APPROOT.'data/'.$sLogFile.'.html', $sHtml);
$sLog = "<?php\n\$aQueriesLog = ".var_export(self::$m_aQueriesLog, true).";";
file_put_contents(APPROOT.'data/'.$sLogFile.'.log', $sLog);
// Cumulate the queries
$sAllQueries = APPROOT.'data/queries.log';
if (file_exists($sAllQueries))
{
// Merge the new queries into the existing log
include($sAllQueries);
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
{
if (!array_key_exists($sQueryId, $aQueriesLog))
{
$aQueriesLog[$sQueryId] = $aOqlData;
}
}
}
else
{
$aQueriesLog = self::$m_aQueriesLog;
}
$sLog = "<?php\n\$aQueriesLog = ".var_export($aQueriesLog, true).";";
file_put_contents($sAllQueries, $sLog);
}
protected static function DbgTrace($value)
{
if (!self::$m_bDebugQuery) return;
$aBacktrace = debug_backtrace();
$iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery;
$sIndent = "";
for ($i = 0 ; $i < $iCallStackPos ; $i++)
{
$sIndent .= " .-=^=-. ";
}
$aCallers = array();
foreach($aBacktrace as $aStackInfo)
{
$aCallers[] = $aStackInfo["function"];
}
$sCallers = "Callstack: ".implode(', ', $aCallers);
$sFunction = "<b title=\"$sCallers\">".$aBacktrace[1]["function"]."</b>";
if (is_string($value))
{
echo "$sIndent$sFunction: $value<br/>\n";
}
else if (is_object($value))
{
echo "$sIndent$sFunction:\n<pre>\n";
print_r($value);
echo "</pre>\n";
}
else
{
echo "$sIndent$sFunction: $value<br/>\n";
}
}
}

View File

@@ -0,0 +1,481 @@
<?php
// Copyright (C) 2015-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/>
/**
* A union of DBObjectSearches
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class DBUnionSearch extends DBSearch
{
protected $aSearches; // source queries
protected $aSelectedClasses; // alias => classes (lowest common ancestors) computed at construction
public function __construct($aSearches)
{
if (count ($aSearches) == 0)
{
throw new CoreException('A DBUnionSearch must be made of at least one search');
}
$this->aSearches = array();
foreach ($aSearches as $oSearch)
{
if ($oSearch instanceof DBUnionSearch)
{
foreach ($oSearch->aSearches as $oSubSearch)
{
$this->aSearches[] = $oSubSearch->DeepClone();
}
}
else
{
$this->aSearches[] = $oSearch->DeepClone();
}
}
$this->ComputeSelectedClasses();
}
/**
* Find the lowest common ancestor for each of the selected class
*/
protected function ComputeSelectedClasses()
{
// 1 - Collect all the column/classes
$aColumnToClasses = array();
foreach ($this->aSearches as $iPos => $oSearch)
{
$aSelected = array_values($oSearch->GetSelectedClasses());
if ($iPos != 0)
{
if (count($aSelected) < count($aColumnToClasses))
{
throw new Exception('Too few selected classes in the subquery #'.($iPos+1));
}
if (count($aSelected) > count($aColumnToClasses))
{
throw new Exception('Too many selected classes in the subquery #'.($iPos+1));
}
}
foreach ($aSelected as $iColumn => $sClass)
{
$aColumnToClasses[$iColumn][] = $sClass;
}
}
// 2 - Build the index column => alias
$oFirstSearch = $this->aSearches[0];
$aColumnToAlias = array_keys($oFirstSearch->GetSelectedClasses());
// 3 - Compute alias => lowest common ancestor
$this->aSelectedClasses = array();
foreach ($aColumnToClasses as $iColumn => $aClasses)
{
$sAlias = $aColumnToAlias[$iColumn];
$sAncestor = MetaModel::GetLowestCommonAncestor($aClasses);
if (is_null($sAncestor))
{
throw new Exception('Could not find a common ancestor for the column '.($iColumn+1).' (Classes: '.implode(', ', $aClasses).')');
}
$this->aSelectedClasses[$sAlias] = $sAncestor;
}
}
public function GetSearches()
{
return $this->aSearches;
}
/**
* Limited to the selected classes
*/
public function GetClassName($sAlias)
{
if (array_key_exists($sAlias, $this->aSelectedClasses))
{
return $this->aSelectedClasses[$sAlias];
}
else
{
throw new CoreException("Invalid class alias '$sAlias'");
}
}
public function GetClass()
{
return reset($this->aSelectedClasses);
}
public function GetClassAlias()
{
reset($this->aSelectedClasses);
return key($this->aSelectedClasses);
}
/**
* Change the class (only subclasses are supported as of now, because the conditions must fit the new class)
* Defaults to the first selected class
* Only the selected classes can be changed
*/
public function ChangeClass($sNewClass, $sAlias = null)
{
if (is_null($sAlias))
{
$sAlias = $this->GetClassAlias();
}
elseif (!array_key_exists($sAlias, $this->aSelectedClasses))
{
// discard silently - necessary when recursing (??? copied from DBObjectSearch)
return;
}
// 1 - identify the impacted column
$iColumn = array_search($sAlias, array_keys($this->aSelectedClasses));
// 2 - change for each search
foreach ($this->aSearches as $oSearch)
{
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
$sSearchAlias = $aSearchAliases[$iColumn];
$oSearch->ChangeClass($sNewClass, $sSearchAlias);
}
// 3 - record the change
$this->aSelectedClasses[$sAlias] = $sNewClass;
}
public function GetSelectedClasses()
{
return $this->aSelectedClasses;
}
/**
* @param array $aSelectedClasses array of aliases
* @throws CoreException
*/
public function SetSelectedClasses($aSelectedClasses)
{
// 1 - change for each search
foreach ($this->aSearches as $oSearch)
{
// Throws an exception if not valid
$oSearch->SetSelectedClasses($aSelectedClasses);
}
// 2 - update the lowest common ancestors
$this->ComputeSelectedClasses();
}
public function IsAny()
{
$bIsAny = true;
foreach ($this->aSearches as $oSearch)
{
if (!$oSearch->IsAny())
{
$bIsAny = false;
break;
}
}
return $bIsAny;
}
public function ResetCondition()
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->ResetCondition();
}
}
public function MergeConditionExpression($oExpression)
{
$aAliases = array_keys($this->aSelectedClasses);
foreach ($this->aSearches as $iSearchIndex => $oSearch)
{
$oClonedExpression = $oExpression->DeepClone();
if ($iSearchIndex != 0)
{
foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias)
{
$oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias);
}
}
$oSearch->MergeConditionExpression($oClonedExpression);
}
}
public function AddConditionExpression($oExpression)
{
$aAliases = array_keys($this->aSelectedClasses);
foreach ($this->aSearches as $iSearchIndex => $oSearch)
{
$oClonedExpression = $oExpression->DeepClone();
if ($iSearchIndex != 0)
{
foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias)
{
$oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias);
}
}
$oSearch->AddConditionExpression($oClonedExpression);
}
}
public function AddNameCondition($sName)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddNameCondition($sName);
}
}
public function AddCondition($sFilterCode, $value, $sOpCode = null)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition($sFilterCode, $value, $sOpCode);
}
}
/**
* Specify a condition on external keys or link sets
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
* Example: infra_list->ci_id->location_id->country
* @param value The value to match (can be an array => IN(val1, val2...)
* @return void
*/
public function AddConditionAdvanced($sAttSpec, $value)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddConditionAdvanced($sAttSpec, $value);
}
}
public function AddCondition_FullText($sFullText)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition_FullText($sFullText);
}
}
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode);
}
}
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode);
}
}
public function Intersect(DBSearch $oFilter)
{
$aSearches = array();
foreach ($this->aSearches as $oSearch)
{
$aSearches[] = $oSearch->Intersect($oFilter);
}
return new DBUnionSearch($aSearches);
}
public function SetInternalParams($aParams)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->SetInternalParams($aParams);
}
}
public function GetInternalParams()
{
$aParams = array();
foreach ($this->aSearches as $oSearch)
{
$aParams = array_merge($oSearch->GetInternalParams(), $aParams);
}
return $aParams;
}
public function GetQueryParams($bExcludeMagicParams = true)
{
$aParams = array();
foreach ($this->aSearches as $oSearch)
{
$aParams = array_merge($oSearch->GetQueryParams($bExcludeMagicParams), $aParams);
}
return $aParams;
}
public function ListConstantFields()
{
// Somewhat complex to implement for unions, for a poor benefit
return array();
}
/**
* Turn the parameters (:xxx) into scalar values in order to easily
* serialize a search
*/
public function ApplyParameters($aArgs)
{
foreach ($this->aSearches as $oSearch)
{
$oSearch->ApplyParameters($aArgs);
}
}
/**
* Overloads for query building
*/
public function ToOQL($bDevelopParams = false, $aContextParams = null)
{
$aSubQueries = array();
foreach ($this->aSearches as $oSearch)
{
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams);
}
$sRet = implode(' UNION ', $aSubQueries);
return $sRet;
}
/**
* Returns a new DBUnionSearch object where duplicates queries have been removed based on their OQLs
*
* @return \DBUnionSearch
*/
public function RemoveDuplicateQueries()
{
$aQueries = array();
$aSearches = array();
foreach ($this->GetSearches() as $oTmpSearch)
{
$sQuery = $oTmpSearch->ToOQL(true);
if (!in_array($sQuery, $aQueries))
{
$aQueries[] = $sQuery;
$aSearches[] = $oTmpSearch;
}
}
$oNewSearch = new DBUnionSearch($aSearches);
return $oNewSearch;
}
////////////////////////////////////////////////////////////////////////////
//
// Construction of the SQL queries
//
////////////////////////////////////////////////////////////////////////////
public function MakeDeleteQuery($aArgs = array())
{
throw new Exception('MakeDeleteQuery is not implemented for the unions!');
}
public function MakeUpdateQuery($aValues, $aArgs = array())
{
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
}
protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
{
if (count($this->aSearches) == 1)
{
return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr);
}
$aSQLQueries = array();
$aAliases = array_keys($this->aSelectedClasses);
foreach ($this->aSearches as $iSearch => $oSearch)
{
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
// The selected classes from the query build perspective are the lowest common ancestors amongst the various queries
// (used when it comes to determine which attributes must be selected)
$aSearchSelectedClasses = array();
foreach ($aSearchAliases as $iColumn => $sSearchAlias)
{
$sAlias = $aAliases[$iColumn];
$aSearchSelectedClasses[$sSearchAlias] = $this->aSelectedClasses[$sAlias];
}
if (is_null($aAttToLoad))
{
$aQueryAttToLoad = null;
}
else
{
// (Eventually) Transform the aliases
$aQueryAttToLoad = array();
foreach ($aAttToLoad as $sAlias => $aAttributes)
{
$iColumn = array_search($sAlias, $aAliases);
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
}
}
if (is_null($aGroupByExpr))
{
$aQueryGroupByExpr = null;
}
else
{
// Clone (and eventually transform) the group by expressions
$aQueryGroupByExpr = array();
$aTranslationData = array();
$aQueryColumns = array_keys($oSearch->GetSelectedClasses());
foreach ($aAliases as $iColumn => $sAlias)
{
$sQueryAlias = $aQueryColumns[$iColumn];
$aTranslationData[$sAlias]['*'] = $sQueryAlias;
$aQueryGroupByExpr[$sAlias.'id'] = new FieldExpression('id', $sQueryAlias);
}
foreach ($aGroupByExpr as $sExpressionAlias => $oExpression)
{
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
}
}
$oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $aQueryGroupByExpr, $aSearchSelectedClasses);
$aSQLQueries[] = $oSubQuery;
}
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr);
//MyHelpers::var_dump_html($oSQLQuery, true);
//MyHelpers::var_dump_html($oSQLQuery->RenderSelect(), true);
if (self::$m_bDebugQuery) $oSQLQuery->DisplayHtml();
return $oSQLQuery;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Algorithm to delete object(s) and maintain data integrity
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -101,16 +101,22 @@ class DeletionPlan
}
if ($aData['mode'] == DEL_MANUAL)
{
$this->m_aToDelete[$sClass][$iId]['issue'] = $sClass.'::'.$iId.' '.Dict::S('UI:Delete:MustBeDeletedManually');
$this->m_bFoundStopper = true;
$this->m_bFoundManualDelete = true;
}
}
}
// Getting and setting time limit are not symetric:
// www.php.net/manual/fr/function.set-time-limit.php#72305
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
foreach($this->m_aToUpdate as $sClass => $aToUpdate)
{
foreach($aToUpdate as $iId => $aData)
{
set_time_limit($iLoopTimeLimit);
$this->m_iToUpdate++;
$oObject = $aData['to_reset'];
@@ -134,9 +140,9 @@ class DeletionPlan
$this->m_bFoundSecurityIssue = true;
}
}
}
}
set_time_limit($iPreviousTimeLimit);
}
public function GetIssues()

View File

@@ -0,0 +1,268 @@
<?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/>
/**
* Design document and associated nodes
* @package Core
*/
namespace Combodo\iTop;
use \DOMDocument;
use \DOMFormatException;
/**
* Class \Combodo\iTop\DesignDocument
*
* A design document is the DOM tree that modelize behaviors. One of its
* characteristics is that it can be altered by the mean of the same kind of document.
*
*/
class DesignDocument extends DOMDocument
{
/**
* @throws \Exception
*/
public function __construct()
{
parent::__construct('1.0', 'UTF-8');
$this->Init();
}
/**
* Overloadable. Called prior to data loading.
*/
protected function Init()
{
$this->registerNodeClass('DOMElement', '\Combodo\iTop\DesignElement');
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
}
/**
* Overload of the standard API
*/
public function load($filename, $options = 0)
{
parent::load($filename, LIBXML_NOBLANKS);
}
/**
* Overload of the standard API
*/
public function save($filename, $options = 0)
{
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
return parent::save($filename, LIBXML_NOBLANKS);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$sXml = $this->saveXML();
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Quote and escape strings for use within an XPath expression
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
* @param $sValue The value to be quoted
* @return string to be used within an XPath
*/
public static function XPathQuote($sValue)
{
if (strpos($sValue, '"') !== false)
{
$aParts = explode('"', $sValue);
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
}
else
{
$sRet = '"'.$sValue.'"';
}
return $sRet;
}
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @param DesignNode|null $oContextNode The node to start the search from
* @return \DOMNodeList
*/
public function GetNodes($sXPath, $oContextNode = null)
{
$oXPath = new \DOMXPath($this);
if (is_null($oContextNode))
{
$oResult = $oXPath->query($sXPath);
}
else
{
$oResult = $oXPath->query($sXPath, $oContextNode);
}
return $oResult;
}
/**
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
* @param $oNode The node to describe
* @return string
*/
public static function GetItopNodePath($oNode)
{
if ($oNode instanceof \DOMDocument) return '';
if (is_null($oNode)) return '';
$sId = $oNode->getAttribute('id');
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
}
}
/**
* DesignElement: helper to read/change the DOM
* @package ModelFactory
*/
class DesignElement extends \DOMElement
{
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @return \DOMNodeList
*/
public function GetNodes($sXPath)
{
return $this->ownerDocument->GetNodes($sXPath, $this);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$oDoc = new DesignDocument();
$oClone = $oDoc->importNode($this->cloneNode(true), true);
$oDoc->appendChild($oClone);
$sXml = $oDoc->saveXML($oClone);
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Returns the node directly under the given node
* @param $sTagName
* @param bool|true $bMustExist
* @return null
* @throws DOMFormatException
*/
public function GetUniqueElement($sTagName, $bMustExist = true)
{
$oNode = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode->nodeName == $sTagName)
{
$oNode = $oChildNode;
break;
}
}
if ($bMustExist && is_null($oNode))
{
throw new DOMFormatException('Missing unique tag: '.$sTagName);
}
return $oNode;
}
/**
* Returns the node directly under the current node, or null if missing
* @param $sTagName
* @return null
* @throws DOMFormatException
*/
public function GetOptionalElement($sTagName)
{
return $this->GetUniqueElement($sTagName, false);
}
/**
* Returns the TEXT of the current node (possibly from several child nodes)
* @param null $sDefault
* @return null|string
*/
public function GetText($sDefault = null)
{
$sText = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode instanceof \DOMText)
{
if (is_null($sText)) $sText = '';
$sText .= $oChildNode->wholeText;
}
}
if (is_null($sText))
{
return $sDefault;
}
else
{
return $sText;
}
}
/**
* Get the TEXT value from a child node
* @param string $sTagName
* @param string|null $sDefault
* @return string
*/
public function GetChildText($sTagName, $sDefault = null)
{
$sRet = $sDefault;
if ($oChild = $this->GetOptionalElement($sTagName))
{
$sRet = $oChild->GetText($sDefault);
}
return $sRet;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -57,26 +57,13 @@ define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception
class Dict
{
protected static $m_bTraceFiles = false;
protected static $m_aEntryFiles = array();
protected static $m_iErrorMode = DICT_ERR_STRING;
protected static $m_sDefaultLanguage = 'EN US';
protected static $m_sCurrentLanguage = null; // No language selected by default
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
protected static $m_aData = array();
public static function EnableTraceFiles()
{
self::$m_bTraceFiles = true;
}
public static function GetEntryFiles()
{
return self::$m_aEntryFiles;
}
protected static $m_sApplicationPrefix = null;
public static function SetDefaultLanguage($sLanguageCode)
{
@@ -119,11 +106,20 @@ class Dict
self::$m_iErrorMode = $iErrorMode;
}
/**
* Returns a localised string from the dictonary
* @param string $sStringCode The code identifying the dictionary entry
* @param string $sDefault Default value if there is no match in the dictionary
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
* @throws DictExceptionMissingString
* @return unknown|Ambigous <>|string
*/
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
// Attempt to find the string in the user language
//
self::InitLangIfNeeded(self::GetUserLanguage());
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
{
// It may happen, when something happens before the dictionnaries get loaded
@@ -138,6 +134,8 @@ class Dict
{
// Attempt to find the string in the default language
//
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
if (array_key_exists($sStringCode, $aDefaultDictionary))
{
@@ -145,6 +143,8 @@ class Dict
}
// Attempt to find the string in english
//
self::InitLangIfNeeded('EN US');
$aDefaultDictionary = self::$m_aData['EN US'];
if (array_key_exists($sStringCode, $aDefaultDictionary))
{
@@ -175,6 +175,12 @@ class Dict
}
/**
* Formats a localized string with numbered placeholders (%1$s...) for the additional arguments
* See vsprintf for more information about the syntax of the placeholders
* @param string $sFormatCode
* @return string
*/
public static function Format($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = self::S($sFormatCode);
@@ -189,43 +195,95 @@ class Dict
return vsprintf($sLocalizedFormat, $aArguments);
}
// sLanguageCode: Code identifying the language i.e. FR-FR
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
// aEntries: Hash array of dictionnary entries
// ~~ or ~* can be used to indicate entries still to be translated.
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
/**
* Initialize a the entries for a given language (replaces the former Add() method)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
* @param hash $aEntries Hash array of dictionnary entries
*/
public static function SetEntries($sLanguageCode, $aEntries)
{
if (self::$m_bTraceFiles)
self::$m_aData[$sLanguageCode] = $aEntries;
}
/**
* Set the list of available languages
* @param hash $aLanguagesList
*/
public static function SetLanguagesList($aLanguagesList)
{
self::$m_aLanguages = $aLanguagesList;
}
/**
* Load a language from the language dictionary, if not already loaded
* @param string $sLangCode Language code
* @return boolean
*/
public static function InitLangIfNeeded($sLangCode)
{
if (array_key_exists($sLangCode, self::$m_aData)) return true;
$bResult = false;
if (function_exists('apc_fetch') && (self::$m_sApplicationPrefix !== null))
{
$aBacktrace = debug_backtrace();
$sFile = $aBacktrace[0]["file"];
foreach($aEntries as $sKey => $sValue)
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
self::$m_aData[$sLangCode] = apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
if (self::$m_aData[$sLangCode] === false)
{
self::$m_aEntryFiles[$sLanguageCode][$sKey] = array(
'file' => $sFile,
'value' => $sValue
);
unset(self::$m_aData[$sLangCode]);
}
else
{
$bResult = true;
}
}
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
if (!$bResult)
{
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
self::$m_aData[$sLanguageCode] = array();
}
foreach($aEntries as $sCode => $sValue)
{
self::$m_aData[$sLanguageCode][$sCode] = self::FilterString($sValue);
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile);
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
{
apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
}
$bResult = true;
}
return $bResult;
}
/**
* Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
*/
public static function EnableCache($sApplicationPrefix)
{
self::$m_sApplicationPrefix = $sApplicationPrefix;
}
/**
* Reset the cached entries (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
*/
public static function ResetCache($sApplicationPrefix)
{
if (function_exists('apc_delete'))
{
foreach(self::$m_aLanguages as $sLang => $void)
{
apc_delete($sApplicationPrefix.'-dict-'.$sLang);
}
}
}
/////////////////////////////////////////////////////////////////////////
/**
* Clone a string in every language (if it exists in that language)
*/
*/
public static function CloneString($sSourceCode, $sDestCode)
{
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
@@ -236,14 +294,14 @@ class Dict
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
$aNotTranslated = array(); // Strings having the same value in both dictionaries
$aOK = array(); // Strings having different values in both dictionaries
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
@@ -251,7 +309,7 @@ class Dict
$aMissing[$sStringCode] = $sValue;
}
}
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -279,57 +337,23 @@ class Dict
{
MyHelpers::var_dump_html(self::$m_aData);
}
public static function InCache($sApplicationPrefix)
{
if (function_exists('apc_fetch'))
{
$bResult = false;
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
//
self::$m_aData = apc_fetch($sApplicationPrefix.'-dict');
if (is_bool(self::$m_aData) && (self::$m_aData === false))
{
self::$m_aData = array();
}
else
{
self::$m_aLanguages = apc_fetch($sApplicationPrefix.'-languages');
if (is_bool(self::$m_aLanguages) && (self::$m_aLanguages === false))
{
self::$m_aLanguages = array();
}
else
{
$bResult = true;
}
}
return $bResult;
}
return false;
}
public static function InitCache($sApplicationPrefix)
{
if (function_exists('apc_store'))
{
apc_store($sApplicationPrefix.'-languages', self::$m_aLanguages);
apc_store($sApplicationPrefix.'-dict', self::$m_aData);
}
}
public static function ResetCache($sApplicationPrefix)
// Only used by the setup to determine the list of languages to display in the initial setup screen
// otherwise replaced by LoadModule by its own handler
// sLanguageCode: Code identifying the language i.e. FR-FR
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
// aEntries: Hash array of dictionnary entries
// ~~ or ~* can be used to indicate entries still to be translated.
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
{
if (function_exists('apc_delete'))
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
{
apc_delete($sApplicationPrefix.'-languages');
apc_delete($sApplicationPrefix.'-dict');
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
self::$m_aData[$sLanguageCode] = array();
}
}
protected static function FilterString($s)
{
return str_replace(array('~~', '~*'), '', $s);
// No need to actually load the strings since it's only used to know the list of languages
// at setup time !!
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Send an email (abstraction for synchronous/asynchronous modes)
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -57,9 +57,6 @@ class EMail
{
$this->m_aData = array();
$this->m_oMessage = Swift_Message::newInstance();
$oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
$this->m_oMessage->setEncoder($oEncoder);
}
/**
@@ -157,6 +154,9 @@ class EMail
protected function SendSynchronous(&$aIssues, $oLog = null)
{
// If the body of the message is in HTML, embed all images based on attachments
$this->EmbedInlineImages();
$this->LoadConfig();
$sTransport = self::$m_oConfig->Get('email_transport');
@@ -177,6 +177,15 @@ class EMail
}
break;
case 'Null':
$oTransport = Swift_NullTransport::newInstance();
break;
case 'LogFile':
$oTransport = Swift_LogFileTransport::newInstance();
$oTransport->setLogFile(APPROOT.'log/mail.log');
break;
case 'PHPMail':
default:
$oTransport = Swift_MailTransport::newInstance();
@@ -184,10 +193,16 @@ class EMail
$oMailer = Swift_Mailer::newInstance($oTransport);
$iSent = $oMailer->send($this->m_oMessage);
$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)
{
$aIssues = array('No valid recipient for this message.');
// Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!!
IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients));
$aIssues = array('Some recipients were invalid.');
return EMAIL_SEND_ERROR;
}
else
@@ -196,9 +211,46 @@ class EMail
return EMAIL_SEND_OK;
}
}
/**
* Reprocess the body of the message (if it is an HTML message)
* to replace the URL of images based on attachments by a link
* to an embedded image (i.e. cid:....)
*/
protected function EmbedInlineImages()
{
if ($this->m_aData['body']['mimeType'] == 'text/html')
{
$oDOMDoc = new DOMDocument();
$oDOMDoc->preserveWhitespace = true;
@$oDOMDoc->loadHTML('<?xml encoding="UTF-8"?>'.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified
$oXPath = new DOMXPath($oDOMDoc);
$sXPath = "//img[@data-img-id]";
$oImagesList = $oXPath->query($sXPath);
if ($oImagesList->length != 0)
{
foreach($oImagesList as $oImg)
{
$iAttId = $oImg->getAttribute('data-img-id');
$oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
if ($oAttachment)
{
$oDoc = $oAttachment->Get('contents');
$oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
$sCid = $this->m_oMessage->embed($oSwiftImage);
$oImg->setAttribute('src', $sCid);
}
}
}
$sHtmlBody = $oDOMDoc->saveHTML();
$this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8');
}
}
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
{
{
if ($bForceSynchronous)
{
return $this->SendSynchronous($aIssues, $oLog);
@@ -253,8 +305,14 @@ class EMail
$this->AddToHeader('References', $sReferences);
}
public function SetBody($sBody, $sMimeType = 'text/html')
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
{
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
{
require_once(APPROOT.'lib/emogrifier/Classes/Emogrifier.php');
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
}
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
$this->m_oMessage->setBody($sBody, $sMimeType);
}
@@ -321,6 +379,11 @@ class EMail
public function GetRecipientTO($bAsString = false)
{
$aRes = $this->m_oMessage->getTo();
if ($aRes === null)
{
// There is no "To" header field
$aRes = array();
}
if ($bAsString)
{
$aStrings = array();
@@ -388,4 +451,75 @@ class EMail
}
?>
/////////////////////////////////////////////////////////////////////////////////////
/**
* Extension to SwiftMailer: "debug" transport that pretends messages have been sent,
* but just log them to a file.
*
* @package Swift
* @author Denis Flaven
*/
class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport
{
protected $sLogFile;
/**
* Sends the given message.
*
* @param Swift_Mime_Message $message
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int The number of sent emails
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$hFile = @fopen($this->sLogFile, 'a');
if ($hFile)
{
$sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n";
$sTxt .= $message->toString()."\n";
@fwrite($hFile, $sTxt);
@fclose($hFile);
}
return parent::send($message, $failedRecipients);
}
public function setLogFile($sFilename)
{
$this->sLogFile = $sFilename;
}
}
/**
* Pretends messages have been sent, but just log them to a file.
*
* @package Swift
* @author Denis Flaven
*/
class Swift_LogFileTransport extends Swift_Transport_LogFileTransport
{
/**
* Create a new LogFileTransport.
*/
public function __construct()
{
call_user_func_array(
array($this, 'Swift_Transport_LogFileTransport::__construct'),
Swift_DependencyContainer::getInstance()
->createDependenciesFor('transport.null')
);
}
/**
* Create a new LogFileTransport instance.
*
* @return Swift_LogFileTransport
*/
public static function newInstance()
{
return new self();
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -107,7 +107,7 @@ class Event extends DBObject implements iDisplay
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
if ($bEditMode) return; // Not editable
if ($bEditMode) return array(); // Not editable
$aDetails = array();
$sClass = get_class($this);
@@ -118,6 +118,7 @@ class Event extends DBObject implements iDisplay
$aDetails[] = array('label' => '<span title="'.MetaModel::GetDescription($sClass, $sAttCode).'">'.MetaModel::GetLabel($sClass, $sAttCode).'</span>', 'value' => $sDisplayValue);
}
$oPage->Details($aDetails);
return array();
}
}
@@ -136,7 +137,10 @@ class EventNotification extends Event
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
"order_by_default" => array('date' => false),
'indexes' => array(
array('object_id'),
)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -328,6 +332,42 @@ class EventWebService extends Event
}
}
class EventRestService extends Event
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_event_restservice",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("operation", array("allowed_values"=>null, "sql"=>"operation", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("json_input", array("allowed_values"=>null, "sql"=>"json_input", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("json_output", array("allowed_values"=>null, "sql"=>"json_output", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("provider", array("allowed_values"=>null, "sql"=>"provider", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'operation', 'version', 'json_input', 'message', 'code', 'json_output', 'provider')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'operation', 'message')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
class EventLoginUsage extends Event
{
public static function Init()
@@ -347,7 +387,7 @@ class EventLoginUsage extends Event
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array())));
$aZList = array('date', 'user_id');
if (MetaModel::IsValidAttCode('Contact', 'name'))
{

View File

@@ -0,0 +1,384 @@
<?php
// Copyright (C) 2015-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/>
/**
* Bulk export: Excel (xlsx) export
*
* @copyright Copyright (C) 2015-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'application/xlsxwriter.class.php');
class ExcelBulkExport extends TabularBulkExport
{
protected $sData;
public function __construct()
{
parent::__construct();
$this->aStatusInfo['status'] = 'not_started';
$this->aStatusInfo['position'] = 0;
}
public function Cleanup()
{
@unlink($this->aStatusInfo['tmp_file']);
parent::Cleanup();
}
public function DisplayUsage(Page $oP)
{
$oP->p(" * xlsx format options:");
$oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
$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'");
}
public function ReadParameters()
{
parent::ReadParameters();
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$sDateFormatRadio = utils::ReadParam('excel_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;
case 'custom':
// 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');
}
}
public function EnumFormParts()
{
return array_merge(parent::EnumFormParts(), array('xlsx_options' => array('formatted_text') ,'interactive_fields_xlsx' => array('interactive_fields_xlsx')));
}
public function DisplayFormPart(WebPage $oP, $sPartId)
{
switch($sPartId)
{
case 'interactive_fields_xlsx':
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_xlsx');
break;
case 'xlsx_options':
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
$oP->add('<input type="checkbox" id="xlsx_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="xlsx_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
$oP->add('</td><td style="vertical-align:top">');
$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('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
$oP->add('<input type="radio" id="excel_date_time_format_default" name="excel_date_format_radio" value="default"'.$sDefaultChecked.'><label for="excel_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
$oP->add('<input type="radio" id="excel_date_time_format_custom" name="excel_date_format_radio" value="custom"'.$sCustomChecked.'><label for="excel_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
$oP->add('</td></tr></table>');
$oP->add('</fieldset>');
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
$oP->add_ready_script(
<<<EOF
$('#excel_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
$('#excel_date_time_format_default').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
$('#excel_date_time_format_custom').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
$('#excel_custom_date_time_format').on('click', function() { $('#excel_date_time_format_custom').prop('checked', true); FormatDatesInPreview('excel', 'xlsx'); }).on('keyup', function() { FormatDatesInPreview('excel', 'xlsx'); });
EOF
);
break;
default:
return parent:: DisplayFormPart($oP, $sPartId);
}
}
protected function SuggestField($sClass, $sAttCode)
{
switch($sAttCode)
{
case 'id': // replace 'id' by 'friendlyname'
$sAttCode = 'friendlyname';
break;
default:
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey)
{
$sAttCode .= '_friendlyname';
}
}
return parent::SuggestField($sClass, $sAttCode);
}
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode != 'id')
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
}
}
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
}
protected function GetValue($oObj, $sAttCode)
{
switch($sAttCode)
{
case 'id':
$sRet = $oObj->GetKey();
break;
default:
$value = $oObj->Get($sAttCode);
if ($value instanceOf ormCaseLog)
{
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
$sText = $value->GetText();
}
else
{
$sText = $value->GetAsPlainText();
}
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $sText));
}
else if ($value instanceOf DBObjectSet)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormCustomFieldsValue)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, "\n", '', $oObj);
}
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeDateTime)
{
// Date and times are formatted using the ISO encoding, not the localized format
if ($oAttDef->IsNull($value))
{
// NOt a valid date
$sRet = '';
}
else
{
$sRet = $value;
}
}
else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
{
$sRet = $oAttDef->GetEditValue($value, $oObj);
}
else
{
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
}
}
}
return $sRet;
}
public function GetHeader()
{
$oSet = new DBObjectSet($this->oSearch);
$this->aStatusInfo['status'] = 'retrieving';
$this->aStatusInfo['tmp_file'] = $this->MakeTmpFile('data');
$this->aStatusInfo['position'] = 0;
$this->aStatusInfo['total'] = $oSet->Count();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$sExtendedAttCode = $aFieldSpec['sFieldSpec'];
$sAttCode = $aFieldSpec['sAttCode'];
$sColLabel = $aFieldSpec['sColLabel'];
switch($sAttCode)
{
case 'id':
$sType = '0';
break;
default:
$oAttDef = MetaModel::GetAttributeDef($aFieldSpec['sClass'], $aFieldSpec['sAttCode']);
$sType = 'string';
if($oAttDef instanceof AttributeDate)
{
$sType = 'date';
}
else if($oAttDef instanceof AttributeDateTime)
{
$sType = 'datetime';
}
}
$aTableHeaders[] = array('label' => $sColLabel, 'type' => $sType);
}
$sRow = json_encode($aTableHeaders);
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab');
if ($hFile === false)
{
throw new Exception('ExcelBulkExport: Failed to open temporary data file: "'.$this->aStatusInfo['tmp_file'].'" for writing.');
}
fwrite($hFile, $sRow."\n");
fclose($hFile);
return '';
}
public function GetNextChunk(&$aStatus)
{
$sRetCode = 'run';
$iPercentage = 0;
$hFile = fopen($this->aStatusInfo['tmp_file'], 'ab');
$oSet = new DBObjectSet($this->oSearch);
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
$this->OptimizeColumnLoad($oSet);
$iCount = 0;
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
$aData = array();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
$oObj = $aRow[$sAlias];
$sField = '';
if ($oObj)
{
$sField = $this->GetValue($oObj, $sAttCode);
}
$aData[] = $sField;
}
fwrite($hFile, json_encode($aData)."\n");
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{
$iPercentage = 100;
$sRetCode = 'done'; // Next phase (GetFooter) will be to build the xlsx file
}
else
{
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
}
if ($iCount < $this->iChunkSize)
{
$sRetCode = 'done';
}
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
return ''; // The actual XLSX file is built in GetFooter();
}
public function GetFooter()
{
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'rb');
if ($hFile === false)
{
throw new Exception('ExcelBulkExport: Failed to open temporary data file: "'.$this->aStatusInfo['tmp_file'].'" for reading.');
}
$sHeaders = fgets($hFile);
$aHeaders = json_decode($sHeaders, true);
$aData = array();
while($sLine = fgets($hFile))
{
$aRow = json_decode($sLine);
$aData[] = $aRow;
}
fclose($hFile);
$fStartExcel = microtime(true);
$writer = new XLSXWriter();
$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
$writer->setDateTimeFormat($oDateTimeFormat->ToExcel());
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
$writer->setDateFormat($oDateFormat->ToExcel());
$writer->setAuthor(UserRights::GetUserFriendlyName());
$aHeaderTypes = array();
$aHeaderNames = array();
foreach($aHeaders as $Header)
{
$aHeaderNames[] = $Header['label'];
$aHeaderTypes[] = $Header['type'];
}
$writer->writeSheet($aData,'Sheet1', $aHeaderTypes, $aHeaderNames);
$fExcelTime = microtime(true) - $fStartExcel;
//$this->aStatistics['excel_build_duration'] = $fExcelTime;
$fTime = microtime(true);
$data = $writer->writeToString();
$fExcelSaveTime = microtime(true) - $fTime;
//$this->aStatistics['excel_write_duration'] = $fExcelSaveTime;
@unlink($this->aStatusInfo['tmp_file']);
return $data;
}
public function GetMimeType()
{
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
public function GetFileExtension()
{
return 'xlsx';
}
public function GetSupportedFormats()
{
return array('xlsx' => Dict::S('Core:BulkExport:XLSXFormat'));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
<?php
// Copyright (C) 2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Bulk export: HTML export
*
* @copyright Copyright (C) 2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class HTMLBulkExport extends TabularBulkExport
{
public function DisplayUsage(Page $oP)
{
$oP->p(" * html format options:");
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
}
public function EnumFormParts()
{
return array_merge(parent::EnumFormParts(), array('interactive_fields_html' => array('interactive_fields_html')));
}
public function DisplayFormPart(WebPage $oP, $sPartId)
{
switch($sPartId)
{
case 'interactive_fields_html':
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_html');
break;
default:
return parent:: DisplayFormPart($oP, $sPartId);
}
}
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode != 'id')
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
}
}
return $this->GetValue($oObj, $sAttCode);
}
protected function GetValue($oObj, $sAttCode)
{
switch($sAttCode)
{
case 'id':
$sRet = $oObj->GetHyperlink();
break;
default:
$value = $oObj->Get($sAttCode);
if ($value instanceof ormCaseLog)
{
$sRet = $value->GetAsSimpleHtml();
}
elseif ($value instanceof ormStopWatch)
{
$sRet = $value->GetTimeSpent();
}
else
{
$sRet = $oObj->GetAsHtml($sAttCode);
}
}
return $sRet;
}
public function GetHeader()
{
$sData = '';
$oSet = new DBObjectSet($this->oSearch);
$this->aStatusInfo['status'] = 'running';
$this->aStatusInfo['position'] = 0;
$this->aStatusInfo['total'] = $oSet->Count();
$sData .= "<table class=\"listResults\">\n";
$sData .= "<thead>\n";
$sData .= "<tr>\n";
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$sData .= "<th>".$aFieldSpec['sColLabel']."</th>\n";
}
$sData .= "</tr>\n";
$sData .= "</thead>\n";
$sData .= "<tbody>\n";
return $sData;
}
public function GetNextChunk(&$aStatus)
{
$sRetCode = 'run';
$iPercentage = 0;
$oSet = new DBObjectSet($this->oSearch);
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
$this->OptimizeColumnLoad($oSet);
$sFirstAlias = $this->oSearch->GetClassAlias();
$iCount = 0;
$sData = '';
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
$oMainObj = $aRow[$sFirstAlias];
$sHilightClass = '';
if ($oMainObj)
{
$sHilightClass = $aRow[$sFirstAlias]->GetHilightClass();
}
if ($sHilightClass != '')
{
$sData .= "<tr class=\"$sHilightClass\">";
}
else
{
$sData .= "<tr>";
}
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
$oObj = $aRow[$sAlias];
$sField = '';
if ($oObj)
{
$sField = $this->GetValue($oObj, $sAttCode);
}
$sValue = ($sField === '') ? '&nbsp;' : $sField;
$sData .= "<td>$sValue</td>";
}
$sData .= "</tr>";
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{
$iPercentage = 100;
}
else
{
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
}
if ($iCount < $this->iChunkSize)
{
$sRetCode = 'done';
}
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
return $sData;
}
public function GetFooter()
{
$sData = "</tbody>\n";
$sData .= "</table>\n";
return $sData;
}
public function GetSupportedFormats()
{
return array('html' => Dict::S('Core:BulkExport:HTMLFormat'));
}
public function GetMimeType()
{
return 'text/html';
}
public function GetFileExtension()
{
return 'html';
}
}

View File

@@ -0,0 +1,340 @@
<?php
/**
* Base class for all possible implementations of HTML Sanitization
*/
abstract class HTMLSanitizer
{
public function __construct()
{
// Do nothing..
}
/**
* Sanitizes the given HTML document
* @param string $sHTML
* @return string
*/
abstract public function DoSanitize($sHTML);
/**
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
* @param string $sHTML
* @return string
*/
public static function Sanitize($sHTML)
{
$sSanitizerClass = MetaModel::GetConfig()->Get('html_sanitizer');
if(!class_exists($sSanitizerClass))
{
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a valid class. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
else if(!is_subclass_of($sSanitizerClass, 'HTMLSanitizer'))
{
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
try
{
$oSanitizer = new $sSanitizerClass();
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
}
catch(Exception $e)
{
if($sSanitizerClass != 'HTMLDOMSanitizer')
{
IssueLog::Warning('Failed to sanitize an HTML string with "'.$sSanitizerClass.'". The following exception occured: '.$e->getMessage());
IssueLog::Warning('Will try to sanitize with HTMLDOMSanitizer.');
// try again with the HTMLDOMSanitizer
$oSanitizer = new HTMLDOMSanitizer();
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
}
else
{
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
IssueLog::Error('The HTML will NOT be sanitized.');
$sCleanHTML = $sHTML;
}
}
return $sCleanHTML;
}
}
/**
* Dummy HTMLSanitizer which does nothing at all!
* Can be used if HTML Sanitization is not important
* (for example when importing "safe" data during an on-boarding)
* and performance is at stake
*
*/
class HTMLNullSanitizer extends HTMLSanitizer
{
/**
* (non-PHPdoc)
* @see HTMLSanitizer::Sanitize()
*/
public function DoSanitize($sHTML)
{
return $sHTML;
}
}
/**
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
* Complete but quite slow
* http://htmlpurifier.org
*/
/*
class HTMLPurifierSanitizer extends HTMLSanitizer
{
protected static $oPurifier = null;
public function __construct()
{
if (self::$oPurifier == null)
{
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
if (!file_exists($sLibPath))
{
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
}
require_once($sLibPath);
$oPurifierConfig = HTMLPurifier_Config::createDefault();
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
$oPurifierConfig->set('URI.AllowedSchemes', array (
'http' => true,
'https' => true,
'data' => true, // This one is not present by default
));
$sPurifierCache = APPROOT.'data/HTMLPurifier';
if (!is_dir($sPurifierCache))
{
mkdir($sPurifierCache);
}
if (!is_dir($sPurifierCache))
{
throw new Exception("Could not create the cache directory '$sPurifierCache'");
}
$oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
}
}
public function DoSanitize($sHTML)
{
$sCleanHtml = self::$oPurifier->purify($sHTML);
return $sCleanHtml;
}
}
*/
class HTMLDOMSanitizer extends HTMLSanitizer
{
protected $oDoc;
protected static $aTagsWhiteList = array(
'html' => array(),
'body' => array(),
'a' => array('href', 'name', 'style'),
'p' => array('style'),
'br' => array(),
'span' => array('style'),
'div' => array('style'),
'b' => array(),
'i' => array(),
'u' => array(),
'em' => array(),
'strong' => array(),
'img' => array('src','style'),
'ul' => array('style'),
'ol' => array('style'),
'li' => array('style'),
'h1' => array('style'),
'h2' => array('style'),
'h3' => array('style'),
'h4' => array('style'),
'nav' => array('style'),
'section' => array('style'),
'code' => array('style'),
'table' => array('style', 'width'),
'thead' => array('style'),
'tbody' => array('style'),
'tr' => array('style'),
'td' => array('style', 'colspan'),
'th' => array('style'),
'fieldset' => array('style'),
'legend' => array('style'),
'font' => array('face', 'color', 'style', 'size'),
'big' => array(),
'small' => array(),
'tt' => array(),
'code' => array(),
'kbd' => array(),
'samp' => array(),
'var' => array(),
'del' => array(),
's' => array(), // strikethrough
'ins' => array(),
'cite' => array(),
'q' => array(),
'hr' => array('style'),
'pre' => array(),
'center' => 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',
);
public function DoSanitize($sHTML)
{
$this->oDoc = new DOMDocument();
$this->oDoc->preserveWhitespace = true;
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
$this->CleanNode($this->oDoc);
$oXPath = new DOMXPath($this->oDoc);
$sXPath = "//body";
$oNodesList = $oXPath->query($sXPath);
if ($oNodesList->length == 0)
{
// No body, save the whole document
$sCleanHtml = $this->oDoc->saveHTML();
}
else
{
// Export only the content of the body tag
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
// remove the body tag itself
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
}
return $sCleanHtml;
}
protected function CleanNode(DOMNode $oElement)
{
$aAttrToRemove = array();
// Gather the attributes to remove
if ($oElement->hasAttributes())
{
foreach($oElement->attributes as $oAttr)
{
$sAttr = strtolower($oAttr->name);
if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)]))
{
// Forbidden (or unknown) attribute
$aAttrToRemove[] = $oAttr->name;
}
else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value))
{
// Invalid content
$aAttrToRemove[] = $oAttr->name;
}
else if ($sAttr == 'style')
{
// Special processing for style tags
$sCleanStyle = $this->CleanStyle($oAttr->value);
if ($sCleanStyle == '')
{
// Invalid content
$aAttrToRemove[] = $oAttr->name;
}
else
{
$oElement->setAttribute($oAttr->name, $sCleanStyle);
}
}
}
// Now remove them
foreach($aAttrToRemove as $sName)
{
$oElement->removeAttribute($sName);
}
}
if ($oElement->hasChildNodes())
{
$aChildElementsToRemove = array();
// Gather the child noes to remove
foreach($oElement->childNodes as $oNode)
{
if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList)))
{
$aChildElementsToRemove[] = $oNode;
}
else if ($oNode instanceof DOMComment)
{
$aChildElementsToRemove[] = $oNode;
}
else
{
// Recurse
$this->CleanNode($oNode);
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
{
$this->ProcessImage($oNode);
}
}
}
// Now remove them
foreach($aChildElementsToRemove as $oDomElement)
{
$oElement->removeChild($oDomElement);
}
}
}
/**
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
* so that we can later reconstruct the full "src" URL when needed
* @param DOMNode $oElement
*/
protected function ProcessImage(DOMNode $oElement)
{
$sSrc = $oElement->getAttribute('src');
$sDownloadUrl = str_replace(array('.', '?'), array('\.', '\?'), INLINEIMAGE_DOWNLOAD_URL); // Escape . and ?
$sUrlPattern = '|'.$sDownloadUrl.'([0-9]+)&s=([0-9a-f]+)|';
if (preg_match($sUrlPattern, $sSrc, $aMatches))
{
$oElement->setAttribute('data-img-id', $aMatches[1]);
$oElement->setAttribute('data-img-secret', $aMatches[2]);
}
}
protected function CleanStyle($sStyle)
{
$aAllowedStyles = array();
$aItems = explode(';', $sStyle);
{
foreach($aItems as $sItem)
{
$aElements = explode(':', trim($sItem));
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
{
$aAllowedStyles[] = trim($sItem);
}
}
}
return implode(';', $aAllowedStyles);
}
protected function IsValidAttributeContent($sAttributeName, $sValue)
{
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
{
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
}
return true;
}
}

View File

@@ -0,0 +1,544 @@
<?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/>
define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_inlineimage&id=');
/**
* Persistent classes (internal): store images referenced inside HTML formatted text fields
*
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class InlineImage extends DBObject
{
public static function Init()
{
$aParams = array
(
'category' => 'addon',
'key_type' => 'autoincrement',
'name_attcode' => array('item_class', 'temp_id'),
'state_attcode' => '',
'reconc_keys' => array(''),
'db_table' => 'inline_image',
'db_key_field' => 'id',
'db_finalclass_field' => '',
'indexes' => array(
array('temp_id'),
array('item_class', 'item_id'),
array('item_org_id'),
),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeDateTime("expire", array("allowed_values"=>null, "sql"=>'expire', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("temp_id", array("allowed_values"=>null, "sql"=>'temp_id', "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("item_class", array("allowed_values"=>null, "sql"=>'item_class', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeObjectKey("item_id", array("class_attcode"=>'item_class', "allowed_values"=>null, "sql"=>'item_id', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeInteger("item_org_id", array("allowed_values"=>null, "sql"=>'item_org_id', "default_value"=>'0', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeString("secret", array("allowed_values"=>null, "sql" => "secret", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_SetZListItems('details', array('temp_id', 'item_class', 'item_id', 'item_org_id'));
MetaModel::Init_SetZListItems('standard_search', array('temp_id', 'item_class', 'item_id'));
MetaModel::Init_SetZListItems('list', array('temp_id', 'item_class', 'item_id' ));
}
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, e.g. 'org_id'
* @return string Filter code, e.g. 'customer_id'
*/
public static function MapContextParam($sContextParam)
{
if ($sContextParam == 'org_id')
{
return 'item_org_id';
}
else
{
return null;
}
}
/**
* Set/Update all of the '_item' fields
* @param DBObject $oItem Container item
* @return void
*/
public function SetItem(DBObject $oItem, $bUpdateOnChange = false)
{
$sClass = get_class($oItem);
$iItemId = $oItem->GetKey();
$this->Set('item_class', $sClass);
$this->Set('item_id', $iItemId);
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$iOrgId = $oItem->Get($sAttCode);
if ($iOrgId > 0)
{
if ($iOrgId != $this->Get('item_org_id'))
{
$this->Set('item_org_id', $iOrgId);
if ($bUpdateOnChange)
{
$this->DBUpdate();
}
}
}
}
}
}
/**
* Give a default value for item_org_id (if relevant...)
* @return void
*/
public function SetDefaultOrgId()
{
// First check that the organization CAN be fetched from the target class
//
$sClass = $this->Get('item_class');
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// Second: check that the organization CAN be fetched from the current user
//
if (MetaModel::IsValidClass('Person'))
{
$aCallSpec = array($sClass, 'MapContextParam');
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
// OK - try it
//
$oCurrentPerson = MetaModel::GetObject('Person', UserRights::GetContactId(), false);
if ($oCurrentPerson)
{
$this->Set('item_org_id', $oCurrentPerson->Get($sAttCode));
}
}
}
}
}
}
}
/**
* When posting a form, finalize the creation of the inline images
* related to the specified object
*
* @param DBObject $oObject
*/
public static function FinalizeInlineImages(DBObject $oObject)
{
$iTransactionId = utils::ReadParam('transaction_id', null);
if (!is_null($iTransactionId))
{
// Attach new (temporary) inline images
$sTempId = utils::GetUploadTempId($iTransactionId);
// The object is being created from a form, check if there are pending inline images for this object
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
while($oInlineImage = $oSet->Fetch())
{
$oInlineImage->SetItem($oObject);
$oInlineImage->Set('temp_id', '');
$oInlineImage->DBUpdate();
}
}
}
/**
* Cleanup the pending images if the form is not submitted
* @param string $sTempId
*/
public static function OnFormCancel($sTempId)
{
// Delete all "pending" InlineImages for this form
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
while($oInlineImage = $oSet->Fetch())
{
$oInlineImage->DBDelete();
}
}
/**
* Parses the supplied HTML fragment to rebuild the attribute src="" for images
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
* the URL is consistent with the current URL of the application.
* @param string $sHtml The HTML fragment to process
* @return string The modified HTML
*/
public static function FixUrls($sHtml)
{
$aNeedles = array();
$aReplacements = array();
// Find img tags with an attribute data-img-id
if (preg_match_all('/<img ([^>]*)data-img-id="([0-9]+)"([^>]*)>/i', $sHtml, $aMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
{
$sUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
foreach($aMatches as $aImgInfo)
{
$sImgTag = $aImgInfo[0][0];
$sSecret = '';
if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches))
{
$sSecret = '&s='.$aSecretMatches[1];
}
$sAttId = $aImgInfo[2][0];
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.htmlentities($sUrl.$sAttId.$sSecret, ENT_QUOTES, 'UTF-8').'"', $sImgTag); // preserve other attributes, must convert & to &amp; to be idempotent with CKEditor
$aNeedles[] = $sImgTag;
$aReplacements[] = $sNewImgTag;
}
$sHtml = str_replace($aNeedles, $aReplacements, $sHtml);
}
return $sHtml;
}
/**
* Get the javascript fragment - to be added to "on document ready" - to adjust (on the fly) the width on Inline Images
*/
public static function FixImagesWidth()
{
$iMaxWidth = (int)MetaModel::GetConfig()->Get('inline_image_max_display_width', 0);
$sJS = '';
if ($iMaxWidth != 0)
{
$sJS =
<<<EOF
$('img[data-img-id]').each(function() {
if ($(this).width() > $iMaxWidth)
{
$(this).css({'max-width': '{$iMaxWidth}px', width: '', height: '', 'max-height': ''});
}
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
}).magnificPopup({type: 'image', closeOnContentClick: true });
EOF
;
}
return $sJS;
}
/**
* Check if an the given mimeType is an image that can be processed by the system
* @param string $sMimeType
* @return boolean
*/
public static function IsImage($sMimeType)
{
if (!function_exists('gd_info')) return false; // no image processing capability on this system
$bRet = false;
$aInfo = gd_info(); // What are the capabilities
switch($sMimeType)
{
case 'image/gif':
return $aInfo['GIF Read Support'];
break;
case 'image/jpeg':
return $aInfo['JPEG Support'];
break;
case 'image/png':
return $aInfo['PNG Support'];
break;
}
return $bRet;
}
/**
* Resize an image so that it fits the maximum width/height defined in the config file
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
* @return ormDocument The resampled image (or the original one if it already fit)
*/
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
{
$img = false;
switch($oImage->GetMimeType())
{
case 'image/gif':
case 'image/jpeg':
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
$aDimensions = null;
return $oImage;
}
if ($img === false)
{
$aDimensions = null;
return $oImage;
}
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$iWidth = imagesx($img);
$iHeight = imagesy($img);
$aDimensions = array('width' => $iWidth, 'height' => $iHeight);
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
if (($iMaxImageSize > 0) && ($iWidth <= $iMaxImageSize) && ($iHeight <= $iMaxImageSize))
{
// No need to resize
return $oImage;
}
$fScale = min($iMaxImageSize / $iWidth, $iMaxImageSize / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$aDimensions['width'] = $iNewWidth;
$aDimensions['height'] = $iNewHeight;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oNewImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oNewImage;
}
}
/**
* Get the (localized) textual representation of the max upload size
* @return string
*/
public static function GetMaxUpload()
{
$iMaxUpload = ini_get('upload_max_filesize');
if (!$iMaxUpload)
{
$sRet = Dict::S('Attachments:UploadNotAllowedOnThisSystem');
}
else
{
$iMaxUpload = utils::ConvertToBytes($iMaxUpload);
if ($iMaxUpload > 1024*1024*1024)
{
$sRet = Dict::Format('Attachment:Max_Go', sprintf('%0.2f', $iMaxUpload/(1024*1024*1024)));
}
else if ($iMaxUpload > 1024*1024)
{
$sRet = Dict::Format('Attachment:Max_Mo', sprintf('%0.2f', $iMaxUpload/(1024*1024)));
}
else
{
$sRet = Dict::Format('Attachment:Max_Ko', sprintf('%0.2f', $iMaxUpload/(1024)));
}
}
return $sRet;
}
/**
* Get the fragment of javascript needed to complete the initialization of
* CKEditor when creating/modifying an object
*
* @param DBObject $oObject The object being edited
* @param string $sTempId The concatenation of session_id().'_'.$iTransactionId.
* @return string The JS fragment to insert in "on document ready"
*/
public static function EnableCKEditorImageUpload(DBObject $oObject, $sTempId)
{
$sObjClass = get_class($oObject);
$iObjKey = $oObject->GetKey();
$sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
$sToggleFullScreen = htmlentities(Dict::S('UI:ToggleFullScreen'), ENT_QUOTES, 'UTF-8');
$sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
return
<<<EOF
// Hook the file upload of all CKEditor instances
$('.htmlEditor').each(function() {
var oEditor = $(this).ckeditorGet();
oEditor.config.extraPlugins = 'font,uploadimage';
oEditor.config.uploadUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php';
oEditor.config.filebrowserBrowseUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php?operation=cke_browse&temp_id=$sTempId&obj_class=$sObjClass&obj_key=$iObjKey';
oEditor.on( 'fileUploadResponse', function( evt ) {
var fileLoader = evt.data.fileLoader;
var xhr = fileLoader.xhr;
var data = evt.data;
try {
var response = JSON.parse( xhr.responseText );
// Error message does not need to mean that upload finished unsuccessfully.
// It could mean that ex. file name was changes during upload due to naming collision.
if ( response.error && response.error.message ) {
data.message = response.error.message;
}
// But !uploaded means error.
if ( !response.uploaded ) {
evt.cancel();
} else {
data.fileName = response.fileName;
data.url = response.url;
// Do not call the default listener.
evt.stop();
}
} catch ( err ) {
// Response parsing error.
data.message = fileLoader.lang.filetools.responseError;
window.console && window.console.log( xhr.responseText );
evt.cancel();
}
} );
oEditor.on( 'fileUploadRequest', function( evt ) {
evt.data.fileLoader.uploadUrl += '?operation=cke_img_upload&temp_id=$sTempId&obj_class=$sObjClass';
}, null, null, 4 ); // Listener with priority 4 will be executed before priority 5.
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 .editor_magnifier').on('click', function() {
oEditor.execCommand('maximize');
if ($(this).closest('.cke_maximized').length != 0)
{
$('#'+oEditor.id+'_toolbar_collapser').trigger('click');
}
});
}
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
;
}
}
/**
* Garbage collector for cleaning "old" temporary InlineImages (and Attachments).
* This background process runs every hour and deletes all temporary InlineImages and Attachments
* whic are are older than one hour.
*/
class InlineImageGC implements iBackgroundProcess
{
public function GetPeriodicity()
{
return 3600; // Runs every 3600 seconds
}
public function Process($iTimeLimit)
{
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time()); // Every temporary InlineImage/Attachment expired will be deleted
$iProcessed = 0;
$sOQL = "SELECT InlineImage WHERE (item_id = 0) AND (expire < '$sDateLimit')";
while (time() < $iTimeLimit)
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
$oSet->OptimizeColumnLoad(array());
$oResult = $oSet->Fetch();
if (is_null($oResult))
{
// Nothing to be done
break;
}
$iProcessed++;
$oResult->DBDelete();
}
$iProcessed2 = 0;
if (class_exists('Attachment'))
{
$sOQL = "SELECT Attachment WHERE (item_id = 0) AND (expire < '$sDateLimit')";
while (time() < $iTimeLimit)
{
// Next one ?
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
$oSet->OptimizeColumnLoad(array());
$oResult = $oSet->Fetch();
if (is_null($oResult))
{
// Nothing to be done
break;
}
$iProcessed2++;
$oResult->DBDelete();
}
}
return "Cleaned $iProcessed old temporary InlineImage(s) and $iProcessed2 old temporary Attachment(s).";
}
}

View File

@@ -28,70 +28,229 @@ class ExecutionKPI
{
static protected $m_bEnabled_Duration = false;
static protected $m_bEnabled_Memory = false;
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
static protected $m_aStats = array();
static protected $m_aStats = array(); // Recurrent operations
static protected $m_aExecData = array(); // One shot operations
protected $m_fStarted = null;
protected $m_iInitialMemory = null;
static public function EnableDuration()
static public function EnableDuration($iLevel)
{
self::$m_bEnabled_Duration = true;
if ($iLevel > 0)
{
self::$m_bEnabled_Duration = true;
if ($iLevel > 1)
{
self::$m_bBlameCaller = true;
}
}
}
static public function EnableMemory()
static public function EnableMemory($iLevel)
{
self::$m_bEnabled_Memory = true;
if ($iLevel > 0)
{
self::$m_bEnabled_Memory = true;
}
}
/**
* @param string sUser A user login or * for all users
*/
static public function SetAllowedUser($sUser)
{
self::$m_sAllowedUser = $sUser;
}
static public function IsEnabled()
{
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory)
{
if ((self::$m_sAllowedUser == '*') || (UserRights::GetUser() == trim(self::$m_sAllowedUser)))
{
return true;
}
}
return false;
}
static public function GetDescription()
{
$aFeatures = array();
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
$sFeatures = implode(', ', $aFeatures);
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
return "KPI logging is active for $sFor. Measures: $sFeatures";
}
static public function ReportStats()
{
if (!self::IsEnabled()) return;
global $fItopStarted;
$sExecId = microtime(); // id to differentiate the hrefs!
$aBeginTimes = array();
foreach (self::$m_aExecData as $aOpStats)
{
$aBeginTimes[] = $aOpStats['time_begin'];
}
array_multisort($aBeginTimes, self::$m_aExecData);
$sTableStyle = 'background-color: #ccc; margin: 10px;';
self::Report("<hr/>");
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
self::Report("<p>log_kpi_user_id: ".MetaModel::GetConfig()->Get('log_kpi_user_id')."</p>");
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>");
self::Report("</thead>");
foreach (self::$m_aExecData as $aOpStats)
{
$sOperation = $aOpStats['op'];
$sBegin = $sEnd = $sDuration = $sMemBegin = $sMemEnd = $sMemPeak = '?';
$sBegin = round($aOpStats['time_begin'], 3);
$sEnd = round($aOpStats['time_end'], 3);
$fDuration = $aOpStats['time_end'] - $aOpStats['time_begin'];
$sDuration = round($fDuration, 3);
if (isset($aOpStats['mem_begin']))
{
$sMemBegin = self::MemStr($aOpStats['mem_begin']);
$sMemEnd = self::MemStr($aOpStats['mem_end']);
if (isset($aOpStats['mem_peak']))
{
$sMemPeak = self::MemStr($aOpStats['mem_peak']);
}
}
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
self::Report("</tr>");
}
self::Report("</table>");
self::Report("</div>");
$aConsolidatedStats = array();
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
echo "<h2>KPIs for $sOperation</h2>\n";
$fTotalOp = 0;
$iTotalOp = 0;
$fMinOp = null;
$fMaxOp = 0;
echo "<ul>\n";
$sMaxOpArguments = null;
foreach ($aOpStats as $sArguments => $aEvents)
{
foreach ($aEvents as $aEventData)
{
$fDuration = $aEventData['time'];
$fTotalOp += $fDuration;
$iTotalOp++;
$fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration);
if ($fDuration > $fMaxOp)
{
$sMaxOpArguments = $sArguments;
$fMaxOp = $fDuration;
}
}
}
$aConsolidatedStats[$sOperation] = array(
'count' => $iTotalOp,
'duration' => $fTotalOp,
'min' => $fMinOp,
'max' => $fMaxOp,
'avg' => $fTotalOp / $iTotalOp,
'max_args' => $sMaxOpArguments
);
}
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
self::Report("</thead>");
foreach ($aConsolidatedStats as $sOperation => $aOpStats)
{
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
$sCount = $aOpStats['count'];
$sDuration = round($aOpStats['duration'], 3);
$sMin = round($aOpStats['min'], 3);
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
$sAvg = round($aOpStats['avg'], 3);
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
self::Report("</tr>");
}
self::Report("</table>");
self::Report("</div>");
self::Report("</div>");
// Report operation details
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
self::Report("<h4>$sOperationHtml</h4>");
self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
self::Report("</thead>");
foreach ($aOpStats as $sArguments => $aEvents)
{
$sHtmlArguments = '<a name="'.md5($sExecId.$sArguments).'"><div style="white-space: pre-wrap;">'.$sArguments.'</div></a>';
if ($aConsolidatedStats[$sOperation]['max_args'] == $sArguments)
{
$sHtmlArguments = '<span style="color: red;">'.$sHtmlArguments.'</span>';
}
if (isset($aEvents[0]['callers']))
{
$sHtmlArguments .= '<div style="padding: 10px;">';
$sHtmlArguments .= '<table border="1" bgcolor="#cfc">';
$sHtmlArguments .= '<tr><td colspan="2" bgcolor="#e9b96">Call stack for the <b>FIRST</b> caller</td></tr>';
foreach ($aEvents[0]['callers'] as $aCall)
{
$sHtmlArguments .= '<tr>';
$sHtmlArguments .= '<td>'.$aCall['Function'].'</td>';
$sHtmlArguments .= '<td>'.$aCall['File'].':'.$aCall['Line'].'</td>';
$sHtmlArguments .= '</tr>';
}
$sHtmlArguments .= '</table>';
$sHtmlArguments .= '</div>';
}
$fTotalInter = 0;
$fMinInter = null;
$fMaxInter = 0;
foreach ($aEvents as $fDuration)
foreach ($aEvents as $aEventData)
{
$fDuration = $aEventData['time'];
$fTotalInter += $fDuration;
$fMinInter = is_null($fMinInter) ? $fDuration : min($fMinInter, $fDuration);
$fMaxInter = max($fMaxInter, $fDuration);
$fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration);
$fMaxOp = max($fMaxOp, $fDuration);
}
$fTotalOp += $fTotalInter;
$iTotalOp++;
$iCountInter = count($aEvents);
$sTotalInter = round($fTotalInter, 3)."s";
if ($iCountInter > 1)
{
$sMinInter = round($fMinInter, 3)."s";
$sMaxInter = round($fMaxInter, 3)."s";
$sTimeDesc = "$sTotalInter (from $sMinInter to $sMaxInter) in $iCountInter times";
}
else
{
$sTimeDesc = "$sTotalInter";
}
echo "<li>Spent $sTimeDesc, on: <span style=\"font-size:60%\">$sArguments</span></li>\n";
$sTotalInter = round($fTotalInter, 3);
$sMinInter = round($fMinInter, 3);
$sMaxInter = round($fMaxInter, 3);
self::Report("<tr>");
self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
self::Report("</tr>");
}
echo "</ul>\n";
echo "<ul>Sumary for $sOperation\n";
echo "<li>Total: $iTotalOp (".round($fTotalOp, 3).")</li>\n";
echo "<li>Min: ".round($fMinOp, 3)."</li>\n";
echo "<li>Max: ".round($fMaxOp, 3)."</li>\n";
echo "<li>Avg: ".round($fTotalOp / $iTotalOp, 3)."</li>\n";
echo "</ul>\n";
self::Report("</table>");
}
}
@@ -105,25 +264,43 @@ class ExecutionKPI
//
public function ComputeAndReport($sOperationDesc)
{
global $fItopStarted;
$aNewEntry = null;
if (self::$m_bEnabled_Duration)
{
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
$this->Report($sOperationDesc.' / duration: '.round($fDuration, 3));
$aNewEntry = array(
'op' => $sOperationDesc,
'time_begin' => $this->m_fStarted - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
);
// Reset for the next operation (if the object is recycled)
$this->m_fStarted = $fStopped;
}
if (self::$m_bEnabled_Memory)
{
$iMemory = self::memory_get_usage();
$iMemoryUsed = $iMemory - $this->m_iInitialMemory;
$this->Report($sOperationDesc.' / memory: '.self::MemStr($iMemoryUsed).' (Total: '.self::MemStr($iMemory).')');
$iCurrentMemory = self::memory_get_usage();
if (is_null($aNewEntry))
{
$aNewEntry = array('op' => $sOperationDesc);
}
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory;
if (function_exists('memory_get_peak_usage'))
{
$iMemoryPeak = memory_get_peak_usage();
$this->Report($sOperationDesc.' / memory peak: '.self::MemStr($iMemoryPeak));
$aNewEntry['mem_peak'] = memory_get_peak_usage();
}
// Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory;
}
if (!is_null($aNewEntry))
{
self::$m_aExecData[] = $aNewEntry;
}
$this->ResetCounters();
}
@@ -133,7 +310,19 @@ class ExecutionKPI
{
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
self::$m_aStats[$sOperation][$sArguments][] = $fDuration;
if (self::$m_bBlameCaller)
{
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration,
'callers' => MyHelpers::get_callstack(1),
);
}
else
{
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration
);
}
}
}
@@ -150,9 +339,11 @@ class ExecutionKPI
}
}
protected function Report($sText)
const HtmlReportFile = 'log/kpi.html';
static protected function Report($sText)
{
echo "$sText<br/>\n";
file_put_contents(APPROOT.self::HtmlReportFile, "$sText\n", FILE_APPEND | LOCK_EX);
}
static protected function MemStr($iMemory)
@@ -204,13 +395,3 @@ class ExecutionKPI
}
}
class ApplicationStartupKPI extends ExecutionKPI
{
public function __construct()
{
global $fItopStarted;
$this->m_fStarted = $fItopStarted;
}
}
?>

View File

@@ -59,8 +59,11 @@ class FileLog
$hLogFile = @fopen($this->m_sFile, 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,288 @@
<?php
// Copyright (C) 2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Reflection API for the MetaModel (partial)
*
* @copyright Copyright (C) 2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Exclude the parent class from the list
*
* @package iTopORM
*/
define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1);
/**
* Include the parent class in the list
*
* @package iTopORM
*/
define('ENUM_CHILD_CLASSES_ALL', 2);
abstract class ModelReflection
{
abstract public function GetClassIcon($sClass, $bImgTag = true);
abstract public function IsValidAttCode($sClass, $sAttCode);
abstract public function GetName($sClass);
abstract public function GetLabel($sClass, $sAttCodeEx);
abstract public function GetValueLabel($sClass, $sAttCode, $sValue);
abstract public function ListAttributes($sClass, $sScope = null);
abstract public function GetAttributeProperty($sClass, $sAttCode, $sPropName, $default = null);
abstract public function GetAllowedValues_att($sClass, $sAttCode);
abstract public function HasChildrenClasses($sClass);
abstract public function GetClasses($sCategories = '', $bExcludeLinks = false);
abstract public function IsValidClass($sClass);
abstract public function IsSameFamilyBranch($sClassA, $sClassB);
abstract public function GetParentClass($sClass);
abstract public function GetFiltersList($sClass);
abstract public function IsValidFilterCode($sClass, $sFilterCode);
abstract public function GetQuery($sOQL);
abstract public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false);
public function DictFormat($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = $this->DictString($sFormatCode);
$aArguments = func_get_args();
array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode)
{
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
return $sFormatCode.' - '.implode(', ', $aArguments);
}
return vsprintf($sLocalizedFormat, $aArguments);
}
abstract public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '');
abstract public function GetRootClass($sClass);
abstract public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP);
}
abstract class QueryReflection
{
/**
* Throws an exception in case of an invalid syntax
*/
abstract public function __construct($sOQL, ModelReflection $oModelReflection);
abstract public function GetClass();
abstract public function GetClassAlias();
}
class ModelReflectionRuntime extends ModelReflection
{
public function __construct()
{
}
public function GetClassIcon($sClass, $bImgTag = true)
{
return MetaModel::GetClassIcon($sClass, $bImgTag);
}
public function IsValidAttCode($sClass, $sAttCode)
{
return MetaModel::IsValidAttCode($sClass, $sAttCode);
}
public function GetName($sClass)
{
return MetaModel::GetName($sClass);
}
public function GetLabel($sClass, $sAttCodeEx)
{
return MetaModel::GetLabel($sClass, $sAttCodeEx);
}
public function GetValueLabel($sClass, $sAttCode, $sValue)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
return $oAttDef->GetValueLabel($sValue);
}
public function ListAttributes($sClass, $sScope = null)
{
$aScope = null;
if ($sScope != null)
{
$aScope = array();
foreach (explode(',', $sScope) as $sScopeClass)
{
$aScope[] = trim($sScopeClass);
}
}
$aAttributes = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
$sAttributeClass = get_class($oAttDef);
if ($aScope != null)
{
foreach ($aScope as $sScopeClass)
{
if (($sAttributeClass == $sScopeClass) || is_subclass_of($sAttributeClass, $sScopeClass))
{
$aAttributes[$sAttCode] = $sAttributeClass;
break;
}
}
}
else
{
$aAttributes[$sAttCode] = $sAttributeClass;
}
}
return $aAttributes;
}
public function GetAttributeProperty($sClass, $sAttCode, $sPropName, $default = null)
{
$ret = $default;
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aParams = $oAttDef->GetParams();
if (array_key_exists($sPropName, $aParams))
{
$ret = $aParams[$sPropName];
}
if ($oAttDef instanceof AttributeHierarchicalKey)
{
if ($sPropName == 'targetclass')
{
$ret = $sClass;
}
}
return $ret;
}
public function GetAllowedValues_att($sClass, $sAttCode)
{
return MetaModel::GetAllowedValues_att($sClass, $sAttCode);
}
public function HasChildrenClasses($sClass)
{
return MetaModel::HasChildrenClasses($sClass);
}
public function GetClasses($sCategories = '', $bExcludeLinks = false)
{
$aClasses = MetaModel::GetClasses($sCategories);
if ($bExcludeLinks)
{
$aExcluded = MetaModel::GetLinkClasses();
$aRes = array();
foreach ($aClasses as $sClass)
{
if (!array_key_exists($sClass, $aExcluded))
{
$aRes[] = $sClass;
}
}
}
else
{
$aRes = $aClasses;
}
return $aRes;
}
public function IsValidClass($sClass)
{
return MetaModel::IsValidClass($sClass);
}
public function IsSameFamilyBranch($sClassA, $sClassB)
{
return MetaModel::IsSameFamilyBranch($sClassA, $sClassB);
}
public function GetParentClass($sClass)
{
return MetaModel::GetParentClass($sClass);
}
public function GetFiltersList($sClass)
{
return MetaModel::GetFiltersList($sClass);
}
public function IsValidFilterCode($sClass, $sFilterCode)
{
return MetaModel::IsValidFilterCode($sClass, $sFilterCode);
}
public function GetQuery($sOQL)
{
return new QueryReflectionRuntime($sOQL, $this);
}
public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
}
public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '')
{
return new RunTimeIconSelectionField($sCode, $sLabel, $defaultValue);
}
public function GetRootClass($sClass)
{
return MetaModel::GetRootClass($sClass);
}
public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP)
{
return MetaModel::EnumChildClasses($sClass, $iOption);
}
}
class QueryReflectionRuntime extends QueryReflection
{
protected $oFilter;
/**
* throws an exception in case of a wrong syntax
*/
public function __construct($sOQL, ModelReflection $oModelReflection)
{
$this->oFilter = DBObjectSearch::FromOQL($sOQL);
}
public function GetClass()
{
return $this->oFilter->GetClass();
}
public function GetClassAlias()
{
return $this->oFilter->GetClassAlias();
}
}

View File

@@ -0,0 +1,120 @@
<?php
// Copyright (C) 2015-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/>
/**
* Module specific customizations:
* The customizations are done in XML, within a module_design section (itop_design/module_designs/module_design)
* The module reads the cusomtizations by the mean of the ModuleDesign API
* @package Core
*/
require_once(APPROOT.'application/utils.inc.php');
require_once(APPROOT.'core/designdocument.class.inc.php');
/**
* Class ModuleDesign
*
* Usage from within a module:
*
* // Fetch the design
* $oDesign = new ModuleDesign('tagada');
*
* // Read data from the root node
* $oRoot = $oDesign->documentElement;
* $oProperties = $oRoot->GetUniqueElement('properties');
* $prop1 = $oProperties->GetChildText('property1');
* $prop2 = $oProperties->GetChildText('property2');
*
* // Read data by searching the entire DOM
* foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
* {
* $sId = $oBrickNode->getAttribute('id');
* $sType = $oBrickNode->getAttribute('xsi:type');
* }
*
* // Search starting a given node
* $oBricks = $oDesign->documentElement->GetUniqueElement('bricks');
* foreach ($oBricks->GetNodes('brick') as $oBrickNode)
* {
* ...
* }
*/
class ModuleDesign extends \Combodo\iTop\DesignDocument
{
/**
* @param string|null $sDesignSourceId Identifier of the section module_design (generally a module name), null to build an empty design
* @throws Exception
*/
public function __construct($sDesignSourceId = null)
{
parent::__construct();
if (!is_null($sDesignSourceId))
{
$this->LoadFromCompiledDesigns($sDesignSourceId);
}
}
/**
* Gets the data where the compiler has left them...
* @param $sDesignSourceId String Identifier of the section module_design (generally a module name)
* @throws Exception
*/
protected function LoadFromCompiledDesigns($sDesignSourceId)
{
$sDesignDir = APPROOT.'env-'.utils::GetCurrentEnvironment().'/core/module_designs/';
$sFile = $sDesignDir.$sDesignSourceId.'.xml';
if (!file_exists($sFile))
{
$aFiles = glob($sDesignDir.'/*.xml');
if (count($aFiles) == 0)
{
$sAvailable = 'none!';
}
else
{
var_dump($aFiles);
$aAvailable = array();
foreach ($aFiles as $sFile)
{
$aAvailable[] = "'".basename($sFile, '.xml')."'";
}
$sAvailable = implode(', ', $aAvailable);
}
throw new Exception("Could not load module design '$sDesignSourceId'. Available designs: $sAvailable");
}
// Silently keep track of errors
libxml_use_internal_errors(true);
libxml_clear_errors();
$this->load($sFile);
//$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
$aErrors = libxml_get_errors();
if (count($aErrors) > 0)
{
$aDisplayErrors = array();
foreach($aErrors as $oXmlError)
{
$aDisplayErrors[] = 'Line '.$oXmlError->line.': '.$oXmlError->message;
}
throw new Exception("Invalid XML in '$sFile'. Errors: ".implode(', ', $aDisplayErrors));
}
}
}

239
core/mutex.class.inc.php Normal file
View File

@@ -0,0 +1,239 @@
<?php
// Copyright (C) 2013-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Class iTopMutex
* A class to serialize the execution of some code sections
* Emulates the API of PECL Mutex class
* Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP.
*
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class iTopMutex
{
protected $sName;
protected $hDBLink;
protected $bLocked; // Whether or not this instance of the Mutex is locked
static protected $aAcquiredLocks = array(); // Number of instances of the Mutex, having the lock, in this page
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
{
// Compute the name of a lock for mysql
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
$sDBName = $oConfig->GetDBName();
$sDBSubname = $oConfig->GetDBSubname();
$this->sName = 'itop.'.$sName;
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
{
// If the name supplied already ends with the expected suffix
// don't add it twice, since the setup may try to detect an already
// running cron job by its mutex, without knowing if the config already exists or not
$this->sName .= $sDBName.$sDBSubname;
}
$this->bLocked = false; // Not yet locked
if (!array_key_exists($this->sName, self::$aAcquiredLocks))
{
self::$aAcquiredLocks[$this->sName] = 0;
}
// It is a MUST to create a dedicated session each time a lock is required, because
// using GET_LOCK anytime on the same session will RELEASE the current and unique session lock (known issue)
$sDBHost = is_null($sDBHost) ? $oConfig->GetDBHost() : $sDBHost;
$sDBUser = is_null($sDBUser) ? $oConfig->GetDBUser() : $sDBUser;
$sDBPwd = is_null($sDBPwd) ? $oConfig->GetDBPwd() : $sDBPwd;
$this->InitMySQLSession($sDBHost, $sDBUser, $sDBPwd);
}
public function __destruct()
{
if ($this->bLocked)
{
$this->Unlock();
}
mysqli_close($this->hDBLink);
}
/**
* Acquire the mutex
*/
public function Lock()
{
if ($this->bLocked)
{
// Lock already acquired
return;
}
if (self::$aAcquiredLocks[$this->sName] == 0)
{
do
{
$res = $this->QueryToScalar("SELECT GET_LOCK('".$this->sName."', 3600)");
if (is_null($res))
{
throw new Exception("Failed to acquire the lock '".$this->sName."'");
}
// $res === '1' means I hold the lock
// $res === '0' means it timed out
}
while ($res !== '1');
}
$this->bLocked = true;
self::$aAcquiredLocks[$this->sName]++;
}
/**
* Attempt to acquire the mutex
* @returns bool True if the mutex is acquired, false if already locked elsewhere
*/
public function TryLock()
{
if ($this->bLocked)
{
return true; // Already acquired
}
if (self::$aAcquiredLocks[$this->sName] > 0)
{
self::$aAcquiredLocks[$this->sName]++;
$this->bLocked = true;
return true;
}
$res = $this->QueryToScalar("SELECT GET_LOCK('".$this->sName."', 0)");
if (is_null($res))
{
throw new Exception("Failed to acquire the lock '".$this->sName."'");
}
// $res === '1' means I hold the lock
// $res === '0' means it timed out
if ($res === '1')
{
$this->bLocked = true;
self::$aAcquiredLocks[$this->sName]++;
}
if (($res !== '1') && ($res !== '0'))
{
$sMsg = 'GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null';
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
return ($res !== '0');
}
/**
* Check if the mutex is locked WITHOUT TRYING TO ACQUIRE IT
* @returns bool True if the mutex is in use, false otherwise
*/
public function IsLocked()
{
if ($this->bLocked)
{
return true; // Already acquired
}
if (self::$aAcquiredLocks[$this->sName] > 0)
{
return true;
}
$res = $this->QueryToScalar("SELECT IS_FREE_LOCK('".$this->sName."')"); // IS_FREE_LOCK detects some error cases that IS_USED_LOCK do not detect
if (is_null($res))
{
$sMsg = "MySQL Error, IS_FREE_LOCK('".$this->sName."') returned null. Error (".mysqli_errno($this->hDBLink).") = '".mysqli_error($this->hDBLink)."'";
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
else if ($res == '1')
{
// Lock is free
return false;
}
return true;
}
/**
* Release the mutex
*/
public function Unlock()
{
if (!$this->bLocked)
{
// ??? the lock is not acquired, exit
return;
}
if (self::$aAcquiredLocks[$this->sName] == 0)
{
return; // Safety net
}
if (self::$aAcquiredLocks[$this->sName] == 1)
{
$res = $this->QueryToScalar("SELECT RELEASE_LOCK('".$this->sName."')");
}
$this->bLocked = false;
self::$aAcquiredLocks[$this->sName]--;
}
public function InitMySQLSession($sHost, $sUser, $sPwd)
{
$aConnectInfo = explode(':', $sHost);
if (count($aConnectInfo) > 1)
{
// Override the default port
$sServer = $aConnectInfo[0];
$iPort = $aConnectInfo[1];
$this->hDBLink = @mysqli_connect($sServer, $sUser, $sPwd, '', $iPort);
}
else
{
$this->hDBLink = @mysqli_connect($sHost, $sUser, $sPwd);
}
if (!$this->hDBLink)
{
throw new Exception("Could not connect to the DB server (host=$sHost, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
}
}
protected function QueryToScalar($sSql)
{
$result = mysqli_query($this->hDBLink, $sSql);
if (!$result)
{
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
}
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH))
{
$res = $aRow[0];
}
else
{
mysqli_free_result($result);
throw new Exception("No result for query '".$sSql."'");
}
mysqli_free_result($result);
return $res;
}
}

View File

@@ -1,4 +0,0 @@
#!/bin/bash
php /usr/share/php/PHP/LexerGenerator/cli.php oql-lexer.plex
php /usr/share/php/PHP/ParserGenerator/cli.php oql-parser.y

View File

@@ -1,3 +0,0 @@
c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\LexerGenerator\cli.php" oql-lexer.plex
c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\ParserGenerator\cli.php" oql-parser.y
pause

View File

@@ -0,0 +1,937 @@
<?php
/* Driver template for the PHP_ParserGenerator parser generator. (PHP port of LEMON)
*/
/**
* This can be used to store both the string representation of
* a token, and any useful meta-data associated with the token.
*
* meta-data should be stored as an array
*/
class ParseyyToken implements ArrayAccess
{
public $string = '';
public $metadata = array();
function __construct($s, $m = array())
{
if ($s instanceof ParseyyToken) {
$this->string = $s->string;
$this->metadata = $s->metadata;
} else {
$this->string = (string) $s;
if ($m instanceof ParseyyToken) {
$this->metadata = $m->metadata;
} elseif (is_array($m)) {
$this->metadata = $m;
}
}
}
function __toString()
{
return $this->string;
}
function offsetExists($offset)
{
return isset($this->metadata[$offset]);
}
function offsetGet($offset)
{
return $this->metadata[$offset];
}
function offsetSet($offset, $value)
{
if ($offset === null) {
if (isset($value[0])) {
$x = ($value instanceof ParseyyToken) ?
$value->metadata : $value;
$this->metadata = array_merge($this->metadata, $x);
return;
}
$offset = count($this->metadata);
}
if ($value === null) {
return;
}
if ($value instanceof ParseyyToken) {
if ($value->metadata) {
$this->metadata[$offset] = $value->metadata;
}
} elseif ($value) {
$this->metadata[$offset] = $value;
}
}
function offsetUnset($offset)
{
unset($this->metadata[$offset]);
}
}
/** The following structure represents a single element of the
* parser's stack. Information stored includes:
*
* + The state number for the parser at this level of the stack.
*
* + The value of the token stored at this level of the stack.
* (In other words, the "major" token.)
*
* + The semantic value stored at this level of the stack. This is
* the information used by the action routines in the grammar.
* It is sometimes called the "minor" token.
*/
class ParseyyStackEntry
{
public $stateno; /* The state-number */
public $major; /* The major token value. This is the code
** number for the token at this stack level */
public $minor; /* The user-supplied minor token value. This
** is the value of the token */
};
// code external to the class is included here
%%
// declare_class is output here
%%
{
/* First off, code is included which follows the "include_class" declaration
** in the input file. */
%%
/* Next is all token values, as class constants
*/
/*
** These constants (all generated automatically by the parser generator)
** specify the various kinds of tokens (terminals) that the parser
** understands.
**
** Each symbol here is a terminal symbol in the grammar.
*/
%%
/* Next are that tables used to determine what action to take based on the
** current state and lookahead token. These tables are used to implement
** functions that take a state number and lookahead value and return an
** action integer.
**
** Suppose the action integer is N. Then the action is determined as
** follows
**
** 0 <= N < self::YYNSTATE Shift N. That is,
** push the lookahead
** token onto the stack
** and goto state N.
**
** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE.
**
** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred.
**
** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its
** input. (and concludes parsing)
**
** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused
** slots in the yy_action[] table.
**
** The action table is constructed as a single large static array $yy_action.
** Given state S and lookahead X, the action is computed as
**
** self::$yy_action[self::$yy_shift_ofst[S] + X ]
**
** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value
** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if
** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that
** the action is not in the table and that self::$yy_default[S] should be used instead.
**
** The formula above is for computing the action when the lookahead is
** a terminal symbol. If the lookahead is a non-terminal (as occurs after
** a reduce action) then the static $yy_reduce_ofst array is used in place of
** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of
** self::YY_SHIFT_USE_DFLT.
**
** The following are the tables generated in this section:
**
** self::$yy_action A single table containing all actions.
** self::$yy_lookahead A table containing the lookahead for each entry in
** yy_action. Used to detect hash collisions.
** self::$yy_shift_ofst For each state, the offset into self::$yy_action for
** shifting terminals.
** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for
** shifting non-terminals after a reduce.
** self::$yy_default Default action for each state.
*/
%%
/* The next thing included is series of defines which control
** various aspects of the generated parser.
** self::YYNOCODE is a number which corresponds
** to no legal terminal or nonterminal number. This
** number is used to fill in empty slots of the hash
** table.
** self::YYFALLBACK If defined, this indicates that one or more tokens
** have fall-back values which should be used if the
** original value of the token will not parse.
** self::YYSTACKDEPTH is the maximum depth of the parser's stack.
** self::YYNSTATE the combined number of states.
** self::YYNRULE the number of rules in the grammar
** self::YYERRORSYMBOL is the code number of the error symbol. If not
** defined, then do no error processing.
*/
%%
/** The next table maps tokens into fallback tokens. If a construct
* like the following:
*
* %fallback ID X Y Z.
*
* appears in the grammer, then ID becomes a fallback token for X, Y,
* and Z. Whenever one of the tokens X, Y, or Z is input to the parser
* but it does not parse, the type of the token is changed to ID and
* the parse is retried before an error is thrown.
*/
static public $yyFallback = array(
%%
);
/**
* Turn parser tracing on by giving a stream to which to write the trace
* and a prompt to preface each trace message. Tracing is turned off
* by making either argument NULL
*
* Inputs:
*
* - A stream resource to which trace output should be written.
* If NULL, then tracing is turned off.
* - A prefix string written at the beginning of every
* line of trace output. If NULL, then tracing is
* turned off.
*
* Outputs:
*
* - None.
* @param resource
* @param string
*/
static function Trace($TraceFILE, $zTracePrompt)
{
if (!$TraceFILE) {
$zTracePrompt = 0;
} elseif (!$zTracePrompt) {
$TraceFILE = 0;
}
self::$yyTraceFILE = $TraceFILE;
self::$yyTracePrompt = $zTracePrompt;
}
/**
* Output debug information to output (php://output stream)
*/
static function PrintTrace()
{
self::$yyTraceFILE = fopen('php://output', 'w');
self::$yyTracePrompt = '';
}
/**
* @var resource|0
*/
static public $yyTraceFILE;
/**
* String to prepend to debug output
* @var string|0
*/
static public $yyTracePrompt;
/**
* @var int
*/
public $yyidx = -1; /* Index of top element in stack */
/**
* @var int
*/
public $yyerrcnt; /* Shifts left before out of the error */
/**
* @var array
*/
public $yystack = array(); /* The parser's stack */
/**
* For tracing shifts, the names of all terminals and nonterminals
* are required. The following table supplies these names
* @var array
*/
static public $yyTokenName = array(
%%
);
/**
* For tracing reduce actions, the names of all rules are required.
* @var array
*/
static public $yyRuleName = array(
%%
);
/**
* This function returns the symbolic name associated with a token
* value.
* @param int
* @return string
*/
function tokenName($tokenType)
{
if ($tokenType === 0) {
return 'End of Input';
}
if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) {
return self::$yyTokenName[$tokenType];
} else {
return "Unknown";
}
}
/**
* The following function deletes the value associated with a
* symbol. The symbol can be either a terminal or nonterminal.
* @param int the symbol code
* @param mixed the symbol's value
*/
static function yy_destructor($yymajor, $yypminor)
{
switch ($yymajor) {
/* Here is inserted the actions which take place when a
** terminal or non-terminal is destroyed. This can happen
** when the symbol is popped from the stack during a
** reduce or during error processing or when a parser is
** being destroyed before it is finished parsing.
**
** Note: during a reduce, the only symbols destroyed are those
** which appear on the RHS of the rule, but which are not used
** inside the C code.
*/
%%
default: break; /* If no destructor action specified: do nothing */
}
}
/**
* Pop the parser's stack once.
*
* If there is a destructor routine associated with the token which
* is popped from the stack, then call it.
*
* Return the major token number for the symbol popped.
* @param ParseyyParser
* @return int
*/
function yy_pop_parser_stack()
{
if (!count($this->yystack)) {
return;
}
$yytos = array_pop($this->yystack);
if (self::$yyTraceFILE && $this->yyidx >= 0) {
fwrite(self::$yyTraceFILE,
self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] .
"\n");
}
$yymajor = $yytos->major;
self::yy_destructor($yymajor, $yytos->minor);
$this->yyidx--;
return $yymajor;
}
/**
* Deallocate and destroy a parser. Destructors are all called for
* all stack elements before shutting the parser down.
*/
function __destruct()
{
while ($this->yyidx >= 0) {
$this->yy_pop_parser_stack();
}
if (is_resource(self::$yyTraceFILE)) {
fclose(self::$yyTraceFILE);
}
}
/**
* Based on the current state and parser stack, get a list of all
* possible lookahead tokens
* @param int
* @return array
*/
function yy_get_expected_tokens($token)
{
$state = $this->yystack[$this->yyidx]->stateno;
$expected = self::$yyExpectedTokens[$state];
if (in_array($token, self::$yyExpectedTokens[$state], true)) {
return $expected;
}
$stack = $this->yystack;
$yyidx = $this->yyidx;
do {
$yyact = $this->yy_find_shift_action($token);
if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
// reduce action
$done = 0;
do {
if ($done++ == 100) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
// too much recursion prevents proper detection
// so give up
return array_unique($expected);
}
$yyruleno = $yyact - self::YYNSTATE;
$this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
$nextstate = $this->yy_find_reduce_action(
$this->yystack[$this->yyidx]->stateno,
self::$yyRuleInfo[$yyruleno]['lhs']);
if (isset(self::$yyExpectedTokens[$nextstate])) {
$expected += self::$yyExpectedTokens[$nextstate];
if (in_array($token,
self::$yyExpectedTokens[$nextstate], true)) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
return array_unique($expected);
}
}
if ($nextstate < self::YYNSTATE) {
// we need to shift a non-terminal
$this->yyidx++;
$x = new ParseyyStackEntry;
$x->stateno = $nextstate;
$x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
$this->yystack[$this->yyidx] = $x;
continue 2;
} elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
// the last token was just ignored, we can't accept
// by ignoring input, this is in essence ignoring a
// syntax error!
return array_unique($expected);
} elseif ($nextstate === self::YY_NO_ACTION) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
// input accepted, but not shifted (I guess)
return $expected;
} else {
$yyact = $nextstate;
}
} while (true);
}
break;
} while (true);
return array_unique($expected);
}
/**
* Based on the parser state and current parser stack, determine whether
* the lookahead token is possible.
*
* The parser will convert the token value to an error token if not. This
* catches some unusual edge cases where the parser would fail.
* @param int
* @return bool
*/
function yy_is_expected_token($token)
{
if ($token === 0) {
return true; // 0 is not part of this
}
$state = $this->yystack[$this->yyidx]->stateno;
if (in_array($token, self::$yyExpectedTokens[$state], true)) {
return true;
}
$stack = $this->yystack;
$yyidx = $this->yyidx;
do {
$yyact = $this->yy_find_shift_action($token);
if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
// reduce action
$done = 0;
do {
if ($done++ == 100) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
// too much recursion prevents proper detection
// so give up
return true;
}
$yyruleno = $yyact - self::YYNSTATE;
$this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
$nextstate = $this->yy_find_reduce_action(
$this->yystack[$this->yyidx]->stateno,
self::$yyRuleInfo[$yyruleno]['lhs']);
if (isset(self::$yyExpectedTokens[$nextstate]) &&
in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
return true;
}
if ($nextstate < self::YYNSTATE) {
// we need to shift a non-terminal
$this->yyidx++;
$x = new ParseyyStackEntry;
$x->stateno = $nextstate;
$x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
$this->yystack[$this->yyidx] = $x;
continue 2;
} elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
if (!$token) {
// end of input: this is valid
return true;
}
// the last token was just ignored, we can't accept
// by ignoring input, this is in essence ignoring a
// syntax error!
return false;
} elseif ($nextstate === self::YY_NO_ACTION) {
$this->yyidx = $yyidx;
$this->yystack = $stack;
// input accepted, but not shifted (I guess)
return true;
} else {
$yyact = $nextstate;
}
} while (true);
}
break;
} while (true);
$this->yyidx = $yyidx;
$this->yystack = $stack;
return true;
}
/**
* Find the appropriate action for a parser given the terminal
* look-ahead token iLookAhead.
*
* If the look-ahead token is YYNOCODE, then check to see if the action is
* independent of the look-ahead. If it is, return the action, otherwise
* return YY_NO_ACTION.
* @param int The look-ahead token
*/
function yy_find_shift_action($iLookAhead)
{
$stateno = $this->yystack[$this->yyidx]->stateno;
/* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
if (!isset(self::$yy_shift_ofst[$stateno])) {
// no shift actions
return self::$yy_default[$stateno];
}
$i = self::$yy_shift_ofst[$stateno];
if ($i === self::YY_SHIFT_USE_DFLT) {
return self::$yy_default[$stateno];
}
if ($iLookAhead == self::YYNOCODE) {
return self::YY_NO_ACTION;
}
$i += $iLookAhead;
if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
self::$yy_lookahead[$i] != $iLookAhead) {
if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
&& ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
if (self::$yyTraceFILE) {
fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
self::$yyTokenName[$iLookAhead] . " => " .
self::$yyTokenName[$iFallback] . "\n");
}
return $this->yy_find_shift_action($iFallback);
}
return self::$yy_default[$stateno];
} else {
return self::$yy_action[$i];
}
}
/**
* Find the appropriate action for a parser given the non-terminal
* look-ahead token $iLookAhead.
*
* If the look-ahead token is self::YYNOCODE, then check to see if the action is
* independent of the look-ahead. If it is, return the action, otherwise
* return self::YY_NO_ACTION.
* @param int Current state number
* @param int The look-ahead token
*/
function yy_find_reduce_action($stateno, $iLookAhead)
{
/* $stateno = $this->yystack[$this->yyidx]->stateno; */
if (!isset(self::$yy_reduce_ofst[$stateno])) {
return self::$yy_default[$stateno];
}
$i = self::$yy_reduce_ofst[$stateno];
if ($i == self::YY_REDUCE_USE_DFLT) {
return self::$yy_default[$stateno];
}
if ($iLookAhead == self::YYNOCODE) {
return self::YY_NO_ACTION;
}
$i += $iLookAhead;
if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
self::$yy_lookahead[$i] != $iLookAhead) {
return self::$yy_default[$stateno];
} else {
return self::$yy_action[$i];
}
}
/**
* Perform a shift action.
* @param int The new state to shift in
* @param int The major token to shift in
* @param mixed the minor token to shift in
*/
function yy_shift($yyNewState, $yyMajor, $yypMinor)
{
$this->yyidx++;
if ($this->yyidx >= self::YYSTACKDEPTH) {
$this->yyidx--;
if (self::$yyTraceFILE) {
fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
}
while ($this->yyidx >= 0) {
$this->yy_pop_parser_stack();
}
/* Here code is inserted which will execute if the parser
** stack ever overflows */
%%
return;
}
$yytos = new ParseyyStackEntry;
$yytos->stateno = $yyNewState;
$yytos->major = $yyMajor;
$yytos->minor = $yypMinor;
array_push($this->yystack, $yytos);
if (self::$yyTraceFILE && $this->yyidx > 0) {
fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
$yyNewState);
fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
for ($i = 1; $i <= $this->yyidx; $i++) {
fprintf(self::$yyTraceFILE, " %s",
self::$yyTokenName[$this->yystack[$i]->major]);
}
fwrite(self::$yyTraceFILE,"\n");
}
}
/**
* The following table contains information about every rule that
* is used during the reduce.
*
* <pre>
* array(
* array(
* int $lhs; Symbol on the left-hand side of the rule
* int $nrhs; Number of right-hand side symbols in the rule
* ),...
* );
* </pre>
*/
static public $yyRuleInfo = array(
%%
);
/**
* The following table contains a mapping of reduce action to method name
* that handles the reduction.
*
* If a rule is not set, it has no handler.
*/
static public $yyReduceMap = array(
%%
);
/* Beginning here are the reduction cases. A typical example
** follows:
** #line <lineno> <grammarfile>
** function yy_r0($yymsp){ ... } // User supplied code
** #line <lineno> <thisfile>
*/
%%
/**
* placeholder for the left hand side in a reduce operation.
*
* For a parser with a rule like this:
* <pre>
* rule(A) ::= B. { A = 1; }
* </pre>
*
* The parser will translate to something like:
*
* <code>
* function yy_r0(){$this->_retvalue = 1;}
* </code>
*/
private $_retvalue;
/**
* Perform a reduce action and the shift that must immediately
* follow the reduce.
*
* For a rule such as:
*
* <pre>
* A ::= B blah C. { dosomething(); }
* </pre>
*
* This function will first call the action, if any, ("dosomething();" in our
* example), and then it will pop three states from the stack,
* one for each entry on the right-hand side of the expression
* (B, blah, and C in our example rule), and then push the result of the action
* back on to the stack with the resulting state reduced to (as described in the .out
* file)
* @param int Number of the rule by which to reduce
*/
function yy_reduce($yyruleno)
{
//int $yygoto; /* The next state */
//int $yyact; /* The next action */
//mixed $yygotominor; /* The LHS of the rule reduced */
//ParseyyStackEntry $yymsp; /* The top of the parser's stack */
//int $yysize; /* Amount to pop the stack */
$yymsp = $this->yystack[$this->yyidx];
if (self::$yyTraceFILE && $yyruleno >= 0
&& $yyruleno < count(self::$yyRuleName)) {
fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
self::$yyTracePrompt, $yyruleno,
self::$yyRuleName[$yyruleno]);
}
$this->_retvalue = $yy_lefthand_side = null;
if (array_key_exists($yyruleno, self::$yyReduceMap)) {
// call the action
$this->_retvalue = null;
$this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
$yy_lefthand_side = $this->_retvalue;
}
$yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
$yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
$this->yyidx -= $yysize;
for ($i = $yysize; $i; $i--) {
// pop all of the right-hand side parameters
array_pop($this->yystack);
}
$yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
if ($yyact < self::YYNSTATE) {
/* If we are not debugging and the reduce action popped at least
** one element off the stack, then we can push the new element back
** onto the stack here, and skip the stack overflow test in yy_shift().
** That gives a significant speed improvement. */
if (!self::$yyTraceFILE && $yysize) {
$this->yyidx++;
$x = new ParseyyStackEntry;
$x->stateno = $yyact;
$x->major = $yygoto;
$x->minor = $yy_lefthand_side;
$this->yystack[$this->yyidx] = $x;
} else {
$this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
}
} elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
$this->yy_accept();
}
}
/**
* The following code executes when the parse fails
*
* Code from %parse_fail is inserted here
*/
function yy_parse_failed()
{
if (self::$yyTraceFILE) {
fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
}
while ($this->yyidx >= 0) {
$this->yy_pop_parser_stack();
}
/* Here code is inserted which will be executed whenever the
** parser fails */
%%
}
/**
* The following code executes when a syntax error first occurs.
*
* %syntax_error code is inserted here
* @param int The major type of the error token
* @param mixed The minor type of the error token
*/
function yy_syntax_error($yymajor, $TOKEN)
{
%%
}
/**
* The following is executed when the parser accepts
*
* %parse_accept code is inserted here
*/
function yy_accept()
{
if (self::$yyTraceFILE) {
fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
}
while ($this->yyidx >= 0) {
$stack = $this->yy_pop_parser_stack();
}
/* Here code is inserted which will be executed whenever the
** parser accepts */
%%
}
/**
* The main parser program.
*
* The first argument is the major token number. The second is
* the token value string as scanned from the input.
*
* @param int $yymajor the token number
* @param mixed $yytokenvalue the token value
* @param mixed ... any extra arguments that should be passed to handlers
*
* @return void
*/
function doParse($yymajor, $yytokenvalue)
{
// $yyact; /* The parser action. */
// $yyendofinput; /* True if we are at the end of input */
$yyerrorhit = 0; /* True if yymajor has invoked an error */
/* (re)initialize the parser, if necessary */
if ($this->yyidx === null || $this->yyidx < 0) {
/* if ($yymajor == 0) return; // not sure why this was here... */
$this->yyidx = 0;
$this->yyerrcnt = -1;
$x = new ParseyyStackEntry;
$x->stateno = 0;
$x->major = 0;
$this->yystack = array();
array_push($this->yystack, $x);
}
$yyendofinput = ($yymajor==0);
if (self::$yyTraceFILE) {
fprintf(
self::$yyTraceFILE,
"%sInput %s\n",
self::$yyTracePrompt,
self::$yyTokenName[$yymajor]
);
}
do {
$yyact = $this->yy_find_shift_action($yymajor);
if ($yymajor < self::YYERRORSYMBOL
&& !$this->yy_is_expected_token($yymajor)
) {
// force a syntax error
$yyact = self::YY_ERROR_ACTION;
}
if ($yyact < self::YYNSTATE) {
$this->yy_shift($yyact, $yymajor, $yytokenvalue);
$this->yyerrcnt--;
if ($yyendofinput && $this->yyidx >= 0) {
$yymajor = 0;
} else {
$yymajor = self::YYNOCODE;
}
} elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
$this->yy_reduce($yyact - self::YYNSTATE);
} elseif ($yyact == self::YY_ERROR_ACTION) {
if (self::$yyTraceFILE) {
fprintf(
self::$yyTraceFILE,
"%sSyntax Error!\n",
self::$yyTracePrompt
);
}
if (self::YYERRORSYMBOL) {
/* A syntax error has occurred.
** The response to an error depends upon whether or not the
** grammar defines an error token "ERROR".
**
** This is what we do if the grammar does define ERROR:
**
** * Call the %syntax_error function.
**
** * Begin popping the stack until we enter a state where
** it is legal to shift the error symbol, then shift
** the error symbol.
**
** * Set the error count to three.
**
** * Begin accepting and shifting new tokens. No new error
** processing will occur until three tokens have been
** shifted successfully.
**
*/
if ($this->yyerrcnt < 0) {
$this->yy_syntax_error($yymajor, $yytokenvalue);
}
$yymx = $this->yystack[$this->yyidx]->major;
if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ) {
if (self::$yyTraceFILE) {
fprintf(
self::$yyTraceFILE,
"%sDiscard input token %s\n",
self::$yyTracePrompt,
self::$yyTokenName[$yymajor]
);
}
$this->yy_destructor($yymajor, $yytokenvalue);
$yymajor = self::YYNOCODE;
} else {
while ($this->yyidx >= 0
&& $yymx != self::YYERRORSYMBOL
&& ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
) {
$this->yy_pop_parser_stack();
}
if ($this->yyidx < 0 || $yymajor==0) {
$this->yy_destructor($yymajor, $yytokenvalue);
$this->yy_parse_failed();
$yymajor = self::YYNOCODE;
} elseif ($yymx != self::YYERRORSYMBOL) {
$u2 = 0;
$this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
}
}
$this->yyerrcnt = 3;
$yyerrorhit = 1;
} else {
/* YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
**
** * Report an error message, and throw away the input token.
**
** * If the input token is $, then fail the parse.
**
** As before, subsequent error messages are suppressed until
** three input tokens have been successfully shifted.
*/
if ($this->yyerrcnt <= 0) {
$this->yy_syntax_error($yymajor, $yytokenvalue);
}
$this->yyerrcnt = 3;
$this->yy_destructor($yymajor, $yytokenvalue);
if ($yyendofinput) {
$this->yy_parse_failed();
}
$yymajor = self::YYNOCODE;
}
} else {
$this->yy_accept();
$yymajor = self::YYNOCODE;
}
} while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
}
}

View File

@@ -0,0 +1,332 @@
<?php
/**
* PHP_LexerGenerator, a php 5 lexer generator.
*
* This lexer generator translates a file in a format similar to
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
*
* PHP version 5
*
* LICENSE:
*
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* * Neither the name of the PHP_LexerGenerator nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category php
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: LexerGenerator.php 294970 2010-02-12 03:46:38Z clockwerx $
* @since File available since Release 0.1.0
*/
/**
* The Lexer generation parser
*/
require_once 'PHP/LexerGenerator/Parser.php';
/**
* Hand-written lexer for lex2php format files
*/
require_once 'PHP/LexerGenerator/Lexer.php';
/**
* The basic home class for the lexer generator. A lexer scans text and
* organizes it into tokens for usage by a parser.
*
* Sample Usage:
* <code>
* require_once 'PHP/LexerGenerator.php';
* $lex = new PHP_LexerGenerator('/path/to/lexerfile.plex');
* </code>
*
* A file named "/path/to/lexerfile.php" will be created.
*
* File format consists of a PHP file containing specially
* formatted comments like so:
*
* <code>
* /*!lex2php
* {@*}
* </code>
*
* All lexer definition files must contain at least two lex2php comment blocks:
* - 1 regex declaration block
* - 1 or more rule declaration blocks
*
* The first lex2php comment is the regex declaration block and must contain
* several processor instruction as well as defining a name for all
* regular expressions. Processor instructions start with
* a "%" symbol and must be:
*
* - %counter
* - %input
* - %token
* - %value
* - %line
*
* token and counter should define the class variables used to define lexer input
* and the index into the input. token and value should be used to define the class
* variables used to store the token number and its textual value. Finally, line
* should be used to define the class variable used to define the current line number
* of scanning.
*
* For example:
* <code>
* /*!lex2php
* %counter {$this->N}
* %input {$this->data}
* %token {$this->token}
* %value {$this->value}
* %line {%this->linenumber}
* {@*}
* </code>
*
* Patterns consist of an identifier containing an letters or an underscore, and
* a descriptive match pattern.
*
* Descriptive match patterns may either be regular expressions (regexes) or
* quoted literal strings. Here are some examples:
*
* <pre>
* pattern = "quoted literal"
* ANOTHER = /[a-zA-Z_]+/
* COMPLEX = @<([a-zA-Z_]+)( +(([a-zA-Z_]+)=((["\'])([^\6]*)\6))+){0,1}>[^<]*</\1>@
* </pre>
*
* Quoted strings must escape the \ and " characters with \" and \\.
*
* Regex patterns must be in Perl-compatible regular expression format (preg).
* special characters (like \t \n or \x3H) can only be used in regexes, all
* \ will be escaped in literal strings.
*
* Sub-patterns may be defined and back-references (like \1) may be used. Any sub-
* patterns detected will be passed to the token handler in the variable
* $yysubmatches.
*
* In addition, lookahead expressions, and once-only expressions are allowed.
* Lookbehind expressions are impossible (scanning always occurs from the
* current position forward), and recursion (?R) can't work and is not allowed.
*
* <code>
* /*!lex2php
* %counter {$this->N}
* %input {$this->data}
* %token {$this->token}
* %value {$this->value}
* %line {%this->linenumber}
* alpha = /[a-zA-Z]/
* alphaplus = /[a-zA-Z]+/
* number = /[0-9]/
* numerals = /[0-9]+/
* whitespace = /[ \t\n]+/
* blah = "$\""
* blahblah = /a\$/
* GAMEEND = @(?:1\-0|0\-1|1/2\-1/2)@
* PAWNMOVE = /P?[a-h]([2-7]|[18]\=(Q|R|B|N))|P?[a-h]x[a-h]([2-7]|[18]\=(Q|R|B|N))/
* {@*}
* </code>
*
* All regexes must be delimited. Any legal preg delimiter can be used (as in @ or / in
* the example above)
*
* Rule lex2php blocks each define a lexer state. You can optionally name the state
* with the %statename processor instruction. State names can be used to transfer to
* a new lexer state with the yybegin() method
*
* <code>
* /*!lexphp
* %statename INITIAL
* blah {
* $this->yybegin(self::INBLAH);
* // note - $this->yybegin(2) would also work
* }
* {@*}
* /*!lex2php
* %statename INBLAH
* ANYTHING {
* $this->yybegin(self::INITIAL);
* // note - $this->yybegin(1) would also work
* }
* {@*}
* </code>
*
* You can maintain a parser state stack simply by using yypushstate() and
* yypopstate() instead of yybegin():
*
* <code>
* /*!lexphp
* %statename INITIAL
* blah {
* $this->yypushstate(self::INBLAH);
* }
* {@*}
* /*!lex2php
* %statename INBLAH
* ANYTHING {
* $this->yypopstate();
* // now INBLAH doesn't care where it was called from
* }
* {@*}
* </code>
*
* Code blocks can choose to skip the current token and cycle to the next token by
* returning "false"
*
* <code>
* /*!lex2php
* WHITESPACE {
* return false;
* }
* {@*}
* </code>
*
* If you wish to re-process the current token in a new state, simply return true.
* If you forget to change lexer state, this will cause an unterminated loop,
* so be careful!
*
* <code>
* /*!lex2php
* "(" {
* $this->yypushstate(self::INPARAMS);
* return true;
* }
* {@*}
* </code>
*
* Lastly, if you wish to cycle to the next matching rule, return any value other than
* true, false or null:
*
* <code>
* /*!lex2php
* "{@" ALPHA {
* if ($this->value == '{@internal') {
* return 'more';
* }
* ...
* }
* "{@internal" {
* ...
* }
* {@*}
* </code>
*
* Note that this procedure is exceptionally inefficient, and it would be far better
* to take advantage of PHP_LexerGenerator's top-down precedence and instead code:
*
* <code>
* /*!lex2php
* "{@internal" {
* ...
* }
* "{@" ALPHA {
* ...
* }
* {@*}
* </code>
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version @package_version@
* @since Class available since Release 0.1.0
* @example TestLexer.plex Example lexer source
* @example TestLexer.php Example lexer generated php code
* @example usage.php Example usage of PHP_LexerGenerator
* @example Lexer.plex File_ChessPGN lexer source (complex)
* @example Lexer.php File_ChessPGN lexer generated php code
*/
class PHP_LexerGenerator
{
/**
* Plex file lexer.
* @var PHP_LexerGenerator_Lexer
*/
private $_lex;
/**
* Plex file parser.
* @var PHP_LexerGenerator_Parser
*/
private $_parser;
/**
* Path to the output PHP file.
* @var string
*/
private $_outfile;
/**
* Debug flag. When set, Parser trace information is generated.
* @var boolean
*/
public $debug = false;
/**
* Create a lexer generator and optionally generate a lexer file.
*
* @param string Optional plex file {@see PHP_LexerGenerator::create}.
* @param string Optional output file {@see PHP_LexerGenerator::create}.
*/
function __construct($lexerfile = '', $outfile = '')
{
if ($lexerfile) {
$this -> create($lexerfile, $outfile);
}
}
/**
* Create a lexer file from its skeleton plex file.
*
* @param string Path to the plex file.
* @param string Optional path to output file. Default is lexerfile with
* extension of ".php".
*/
function create($lexerfile, $outfile = '')
{
$this->_lex = new PHP_LexerGenerator_Lexer(file_get_contents($lexerfile));
$info = pathinfo($lexerfile);
if ($outfile) {
$this->outfile = $outfile;
} else {
$this->outfile = $info['dirname'] . DIRECTORY_SEPARATOR .
substr($info['basename'], 0,
strlen($info['basename']) - strlen($info['extension'])) . 'php';
}
$this->_parser = new PHP_LexerGenerator_Parser($this->outfile, $this->_lex);
if ($this -> debug) {
$this->_parser->PrintTrace();
}
while ($this->_lex->advance($this->_parser)) {
$this->_parser->doParse($this->_lex->token, $this->_lex->value);
}
$this->_parser->doParse(0, 0);
}
}
//$a = new PHP_LexerGenerator('/development/File_ChessPGN/ChessPGN/Lexer.plex');
?>

View File

@@ -0,0 +1,55 @@
<?php
/**
* PHP_LexerGenerator, a php 5 lexer generator.
*
* Exception classes for the lexer generator
*
* PHP version 5
*
* LICENSE:
*
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* * Neither the name of the PHP_LexerGenerator nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category php
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
*/
require_once 'PEAR/Exception.php';
/**
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version @package_version@
* @since File available since Release 0.1.0
*/
class PHP_LexerGenerator_Exception extends PEAR_Exception {}
?>

View File

@@ -0,0 +1,533 @@
<?php
/**
* PHP_LexerGenerator, a php 5 lexer generator.
*
* This lexer generator translates a file in a format similar to
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
*
* PHP version 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category php
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Lexer.php 246683 2007-11-22 04:43:52Z instance $
* @since File available since Release 0.1.0
*/
require_once 'PHP/LexerGenerator/Parser.php';
/**
* Token scanner for plex files.
*
* This scanner detects comments beginning with "/*!lex2php" and
* then returns their components (processing instructions, patterns, strings
* action code, and regexes)
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version @package_version@
* @since Class available since Release 0.1.0
*/
class PHP_LexerGenerator_Lexer
{
private $data;
private $N;
private $state;
/**
* Current line number in input
* @var int
*/
public $line;
/**
* Number of scanning errors detected
* @var int
*/
public $errors = 0;
/**
* integer identifier of the current token
* @var int
*/
public $token;
/**
* string content of current token
* @var string
*/
public $value;
const CODE = PHP_LexerGenerator_Parser::CODE;
const COMMENTEND = PHP_LexerGenerator_Parser::COMMENTEND;
const COMMENTSTART = PHP_LexerGenerator_Parser::COMMENTSTART;
const PATTERN = PHP_LexerGenerator_Parser::PATTERN;
const PHPCODE = PHP_LexerGenerator_Parser::PHPCODE;
const PI = PHP_LexerGenerator_Parser::PI;
const QUOTE = PHP_LexerGenerator_Parser::QUOTE;
const SINGLEQUOTE = PHP_LexerGenerator_Parser::SINGLEQUOTE;
const SUBPATTERN = PHP_LexerGenerator_Parser::SUBPATTERN;
/**
* prepare scanning
* @param string the input
*/
function __construct($data)
{
$this->data = str_replace("\r\n", "\n", $data);
$this->N = 0;
$this->line = 1;
$this->state = 'Start';
$this->errors = 0;
}
/**
* Output an error message
* @param string
*/
private function error($msg)
{
echo 'Error on line ' . $this->line . ': ' . $msg;
$this->errors++;
}
/**
* Initial scanning state lexer
* @return boolean
*/
private function lexStart()
{
if ($this->N >= strlen($this->data)) {
return false;
}
$a = strpos($this->data, '/*!lex2php' . "\n", $this->N);
if ($a === false) {
$this->value = substr($this->data, $this->N);
$this->N = strlen($this->data);
$this->token = self::PHPCODE;
return true;
}
if ($a > $this->N) {
$this->value = substr($this->data, $this->N, $a - $this->N);
$this->N = $a;
$this->token = self::PHPCODE;
return true;
}
$this->value = '/*!lex2php' . "\n";
$this->N += 11; // strlen("/*lex2php\n")
$this->token = self::COMMENTSTART;
$this->state = 'Declare';
return true;
}
/**
* lexer for top-level canning state after the initial declaration comment
* @return boolean
*/
private function lexStartNonDeclare()
{
if ($this->N >= strlen($this->data)) {
return false;
}
$a = strpos($this->data, '/*!lex2php' . "\n", $this->N);
if ($a === false) {
$this->value = substr($this->data, $this->N);
$this->N = strlen($this->data);
$this->token = self::PHPCODE;
return true;
}
if ($a > $this->N) {
$this->value = substr($this->data, $this->N, $a - $this->N);
$this->N = $a;
$this->token = self::PHPCODE;
return true;
}
$this->value = '/*!lex2php' . "\n";
$this->N += 11; // strlen("/*lex2php\n")
$this->token = self::COMMENTSTART;
$this->state = 'Rule';
return true;
}
/**
* lexer for declaration comment state
* @return boolean
*/
private function lexDeclare()
{
while (true) {
$this -> skipWhitespaceEol();
if (
$this->N + 1 >= strlen($this->data)
|| $this->data[$this->N] != '/'
|| $this->data[$this->N + 1] != '/'
) {
break;
}
// Skip single-line comment
while (
$this->N < strlen($this->data)
&& $this->data[$this->N] != "\n"
) {
++$this->N;
}
}
if ($this->data[$this->N] == '*' && $this->data[$this->N + 1] == '/') {
$this->state = 'StartNonDeclare';
$this->value = '*/';
$this->N += 2;
$this->token = self::COMMENTEND;
return true;
}
if (preg_match('/\G%([a-z]+)/', $this->data, $token, null, $this->N)) {
$this->value = $token[1];
$this->N += strlen($token[1]) + 1;
$this->state = 'DeclarePI';
$this->token = self::PI;
return true;
}
if (preg_match('/\G[a-zA-Z_][a-zA-Z0-9_]*/', $this->data, $token, null, $this->N)) {
$this->value = $token[0];
$this->token = self::PATTERN;
$this->N += strlen($token[0]);
$this->state = 'DeclareEquals';
return true;
}
$this->error('expecting declaration of sub-patterns');
return false;
}
/**
* lexer for processor instructions within declaration comment
* @return boolean
*/
private function lexDeclarePI()
{
$this -> skipWhitespace();
if ($this->data[$this->N] == "\n") {
$this->N++;
$this->state = 'Declare';
$this->line++;
return $this->lexDeclare();
}
if ($this->data[$this->N] == '{') {
return $this->lexCode();
}
if (!preg_match("/\G[^\n]+/", $this->data, $token, null, $this->N)) {
$this->error('Unexpected end of file');
return false;
}
$this->value = $token[0];
$this->N += strlen($this->value);
$this->token = self::SUBPATTERN;
return true;
}
/**
* lexer for processor instructions inside rule comments
* @return boolean
*/
private function lexDeclarePIRule()
{
$this -> skipWhitespace();
if ($this->data[$this->N] == "\n") {
$this->N++;
$this->state = 'Rule';
$this->line++;
return $this->lexRule();
}
if ($this->data[$this->N] == '{') {
return $this->lexCode();
}
if (!preg_match("/\G[^\n]+/", $this->data, $token, null, $this->N)) {
$this->error('Unexpected end of file');
return false;
}
$this->value = $token[0];
$this->N += strlen($this->value);
$this->token = self::SUBPATTERN;
return true;
}
/**
* lexer for the state representing scanning between a pattern and the "=" sign
* @return boolean
*/
private function lexDeclareEquals()
{
$this -> skipWhitespace();
if ($this->N >= strlen($this->data)) {
$this->error('unexpected end of input, expecting "=" for sub-pattern declaration');
}
if ($this->data[$this->N] != '=') {
$this->error('expecting "=" for sub-pattern declaration');
return false;
}
$this->N++;
$this->state = 'DeclareRightside';
$this -> skipWhitespace();
if ($this->N >= strlen($this->data)) {
$this->error('unexpected end of file, expecting right side of sub-pattern declaration');
return false;
}
return $this->lexDeclareRightside();
}
/**
* lexer for the right side of a pattern, detects quotes or regexes
* @return boolean
*/
private function lexDeclareRightside()
{
if ($this->data[$this->N] == "\n") {
$this->state = 'lexDeclare';
$this->N++;
$this->line++;
return $this->lexDeclare();
}
if ($this->data[$this->N] == '"') {
return $this->lexQuote();
}
if ($this->data[$this->N] == '\'') {
return $this->lexQuote('\'');
}
$this -> skipWhitespace();
// match a pattern
$test = $this->data[$this->N];
$token = $this->N + 1;
$a = 0;
do {
if ($a++) {
$token++;
}
$token = strpos($this->data, $test, $token);
} while ($token !== false && ($this->data[$token - 1] == '\\'
&& $this->data[$token - 2] != '\\'));
if ($token === false) {
$this->error('Unterminated regex pattern (started with "' . $test . '"');
return false;
}
if (substr_count($this->data, "\n", $this->N, $token - $this->N)) {
$this->error('Regex pattern extends over multiple lines');
return false;
}
$this->value = substr($this->data, $this->N + 1, $token - $this->N - 1);
// unescape the regex marker
// we will re-escape when creating the final regex
$this->value = str_replace('\\' . $test, $test, $this->value);
$this->N = $token + 1;
$this->token = self::SUBPATTERN;
return true;
}
/**
* lexer for quoted literals
* @return boolean
*/
private function lexQuote($quote = '"')
{
$token = $this->N + 1;
$a = 0;
do {
if ($a++) {
$token++;
}
$token = strpos($this->data, $quote, $token);
} while ($token !== false && $token < strlen($this->data) &&
($this->data[$token - 1] == '\\' && $this->data[$token - 2] != '\\'));
if ($token === false) {
$this->error('unterminated quote');
return false;
}
if (substr_count($this->data, "\n", $this->N, $token - $this->N)) {
$this->error('quote extends over multiple lines');
return false;
}
$this->value = substr($this->data, $this->N + 1, $token - $this->N - 1);
$this->value = str_replace('\\'.$quote, $quote, $this->value);
$this->value = str_replace('\\\\', '\\', $this->value);
$this->N = $token + 1;
if ($quote == '\'' ) {
$this->token = self::SINGLEQUOTE;
} else {
$this->token = self::QUOTE;
}
return true;
}
/**
* lexer for rules
* @return boolean
*/
private function lexRule()
{
while (
$this->N < strlen($this->data)
&& (
$this->data[$this->N] == ' '
|| $this->data[$this->N] == "\t"
|| $this->data[$this->N] == "\n"
) || (
$this->N < strlen($this->data) - 1
&& $this->data[$this->N] == '/'
&& $this->data[$this->N + 1] == '/'
)
) {
if ( $this->data[$this->N] == '/' && $this->data[$this->N + 1] == '/' ) {
// Skip single line comments
$next_newline = strpos($this->data, "\n", $this->N) + 1;
if ($next_newline) {
$this->N = $next_newline;
} else {
$this->N = sizeof($this->data);
}
$this->line++;
} else {
if ($this->data[$this->N] == "\n") {
$this->line++;
}
$this->N++; // skip all whitespace
}
}
if ($this->N >= strlen($this->data)) {
$this->error('unexpected end of input, expecting rule declaration');
}
if ($this->data[$this->N] == '*' && $this->data[$this->N + 1] == '/') {
$this->state = 'StartNonDeclare';
$this->value = '*/';
$this->N += 2;
$this->token = self::COMMENTEND;
return true;
}
if ($this->data[$this->N] == '\'') {
return $this->lexQuote('\'');
}
if (preg_match('/\G%([a-zA-Z_]+)/', $this->data, $token, null, $this->N)) {
$this->value = $token[1];
$this->N += strlen($token[1]) + 1;
$this->state = 'DeclarePIRule';
$this->token = self::PI;
return true;
}
if ($this->data[$this->N] == "{") {
return $this->lexCode();
}
if ($this->data[$this->N] == '"') {
return $this->lexQuote();
}
if (preg_match('/\G[a-zA-Z_][a-zA-Z0-9_]*/', $this->data, $token, null, $this->N)) {
$this->value = $token[0];
$this->N += strlen($token[0]);
$this->token = self::SUBPATTERN;
return true;
} else {
$this->error('expecting token rule (quotes or sub-patterns)');
return false;
}
}
/**
* lexer for php code blocks
* @return boolean
*/
private function lexCode()
{
$cp = $this->N + 1;
for ($level = 1; $cp < strlen($this->data) && ($level > 1 || $this->data[$cp] != '}'); $cp++) {
if ($this->data[$cp] == '{') {
$level++;
} elseif ($this->data[$cp] == '}') {
$level--;
} elseif ($this->data[$cp] == '/' && $this->data[$cp + 1] == '/') {
/* Skip C++ style comments */
$cp += 2;
$z = strpos($this->data, "\n", $cp);
if ($z === false) {
$cp = strlen($this->data);
break;
}
$cp = $z;
} elseif ($this->data[$cp] == "'" || $this->data[$cp] == '"') {
/* String a character literals */
$startchar = $this->data[$cp];
$prevc = 0;
for ($cp++; $cp < strlen($this->data) && ($this->data[$cp] != $startchar || $prevc === '\\'); $cp++) {
if ($prevc === '\\') {
$prevc = 0;
} else {
$prevc = $this->data[$cp];
}
}
}
}
if ($cp >= strlen($this->data)) {
$this->error("PHP code starting on this line is not terminated before the end of the file.");
$this->error++;
return false;
} else {
$this->value = substr($this->data, $this->N + 1, $cp - $this->N - 1);
$this->token = self::CODE;
$this->N = $cp + 1;
return true;
}
}
/**
* Skip whitespace characters
*/
private function skipWhitespace() {
while (
$this->N < strlen($this->data)
&& (
$this->data[$this->N] == ' '
|| $this->data[$this->N] == "\t"
)
) {
$this->N++; // skip whitespace
}
}
/**
* Skip whitespace and EOL characters
*/
private function skipWhitespaceEol() {
while (
$this->N < strlen($this->data)
&& (
$this->data[$this->N] == ' '
|| $this->data[$this->N] == "\t"
|| $this->data[$this->N] == "\n"
)
) {
if ($this->data[$this->N] == "\n") {
++$this -> line;
}
$this->N++; // skip whitespace
}
}
/**
* Primary scanner
*
* In addition to lexing, this properly increments the line number of lexing.
* This calls the proper sub-lexer based on the parser state
* @param unknown_type $parser
* @return unknown
*/
public function advance($parser)
{
if ($this->N >= strlen($this->data)) {
return false;
}
if ($this->{'lex' . $this->state}()) {
$this->line += substr_count($this->value, "\n");
return true;
}
return false;
}
}
?>

View File

@@ -0,0 +1,492 @@
State 0:
start ::= * lexfile
lexfile ::= * declare rules
lexfile ::= * declare PHPCODE rules
lexfile ::= * PHPCODE declare rules
lexfile ::= * PHPCODE declare PHPCODE rules
declare ::= * COMMENTSTART declarations COMMENTEND
PHPCODE shift 17
COMMENTSTART shift 8
start accept
lexfile shift 52
declare shift 6
State 1:
rules ::= reset_rules * rule COMMENTEND
rules ::= reset_rules * PI SUBPATTERN rule COMMENTEND
rules ::= reset_rules * rule COMMENTEND PHPCODE
rules ::= reset_rules * PI SUBPATTERN rule COMMENTEND PHPCODE
rule ::= * rule_subpattern CODE
rule ::= * rule rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
PI shift 29
SUBPATTERN shift 50
QUOTE shift 51
rule shift 12
rule_subpattern shift 18
State 2:
rules ::= COMMENTSTART * rule COMMENTEND
rules ::= COMMENTSTART * PI SUBPATTERN rule COMMENTEND
rules ::= COMMENTSTART * rule COMMENTEND PHPCODE
rules ::= COMMENTSTART * PI SUBPATTERN rule COMMENTEND PHPCODE
rule ::= * rule_subpattern CODE
rule ::= * rule rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
PI shift 34
SUBPATTERN shift 50
QUOTE shift 51
rule shift 11
rule_subpattern shift 18
State 3:
rules ::= COMMENTSTART PI SUBPATTERN * rule COMMENTEND
rules ::= COMMENTSTART PI SUBPATTERN * rule COMMENTEND PHPCODE
rule ::= * rule_subpattern CODE
rule ::= * rule rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
SUBPATTERN shift 50
QUOTE shift 51
rule shift 10
rule_subpattern shift 18
State 4:
lexfile ::= PHPCODE declare * rules
lexfile ::= PHPCODE declare * PHPCODE rules
rules ::= * COMMENTSTART rule COMMENTEND
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
rules ::= * reset_rules rule COMMENTEND
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
rules ::= * reset_rules rule COMMENTEND PHPCODE
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
reset_rules ::= * rules COMMENTSTART
PHPCODE shift 7
COMMENTSTART shift 2
rules shift 25
reset_rules shift 1
State 5:
rules ::= reset_rules PI SUBPATTERN * rule COMMENTEND
rules ::= reset_rules PI SUBPATTERN * rule COMMENTEND PHPCODE
rule ::= * rule_subpattern CODE
rule ::= * rule rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
SUBPATTERN shift 50
QUOTE shift 51
rule shift 13
rule_subpattern shift 18
State 6:
lexfile ::= declare * rules
lexfile ::= declare * PHPCODE rules
rules ::= * COMMENTSTART rule COMMENTEND
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
rules ::= * reset_rules rule COMMENTEND
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
rules ::= * reset_rules rule COMMENTEND PHPCODE
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
reset_rules ::= * rules COMMENTSTART
PHPCODE shift 9
COMMENTSTART shift 2
rules shift 33
reset_rules shift 1
State 7:
lexfile ::= PHPCODE declare PHPCODE * rules
rules ::= * COMMENTSTART rule COMMENTEND
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
rules ::= * reset_rules rule COMMENTEND
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
rules ::= * reset_rules rule COMMENTEND PHPCODE
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
reset_rules ::= * rules COMMENTSTART
COMMENTSTART shift 2
rules shift 27
reset_rules shift 1
State 8:
declare ::= COMMENTSTART * declarations COMMENTEND
declarations ::= * processing_instructions pattern_declarations
processing_instructions ::= * PI SUBPATTERN
processing_instructions ::= * PI CODE
processing_instructions ::= * processing_instructions PI SUBPATTERN
processing_instructions ::= * processing_instructions PI CODE
PI shift 23
declarations shift 28
processing_instructions shift 14
State 9:
lexfile ::= declare PHPCODE * rules
rules ::= * COMMENTSTART rule COMMENTEND
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
rules ::= * reset_rules rule COMMENTEND
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
rules ::= * reset_rules rule COMMENTEND PHPCODE
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
reset_rules ::= * rules COMMENTSTART
COMMENTSTART shift 2
rules shift 31
reset_rules shift 1
State 10:
rules ::= COMMENTSTART PI SUBPATTERN rule * COMMENTEND
rules ::= COMMENTSTART PI SUBPATTERN rule * COMMENTEND PHPCODE
rule ::= rule * rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
COMMENTEND shift 30
SUBPATTERN shift 50
QUOTE shift 51
rule_subpattern shift 19
State 11:
rules ::= COMMENTSTART rule * COMMENTEND
rules ::= COMMENTSTART rule * COMMENTEND PHPCODE
rule ::= rule * rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
COMMENTEND shift 32
SUBPATTERN shift 50
QUOTE shift 51
rule_subpattern shift 19
State 12:
rules ::= reset_rules rule * COMMENTEND
rules ::= reset_rules rule * COMMENTEND PHPCODE
rule ::= rule * rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
COMMENTEND shift 35
SUBPATTERN shift 50
QUOTE shift 51
rule_subpattern shift 19
State 13:
rules ::= reset_rules PI SUBPATTERN rule * COMMENTEND
rules ::= reset_rules PI SUBPATTERN rule * COMMENTEND PHPCODE
rule ::= rule * rule_subpattern CODE
rule_subpattern ::= * QUOTE
rule_subpattern ::= * SUBPATTERN
rule_subpattern ::= * rule_subpattern QUOTE
rule_subpattern ::= * rule_subpattern SUBPATTERN
COMMENTEND shift 24
SUBPATTERN shift 50
QUOTE shift 51
rule_subpattern shift 19
State 14:
declarations ::= processing_instructions * pattern_declarations
processing_instructions ::= processing_instructions * PI SUBPATTERN
processing_instructions ::= processing_instructions * PI CODE
pattern_declarations ::= * PATTERN subpattern
pattern_declarations ::= * pattern_declarations PATTERN subpattern
PI shift 20
PATTERN shift 16
pattern_declarations shift 26
State 15:
pattern_declarations ::= pattern_declarations PATTERN * subpattern
subpattern ::= * QUOTE
subpattern ::= * SUBPATTERN
subpattern ::= * subpattern QUOTE
subpattern ::= * subpattern SUBPATTERN
SUBPATTERN shift 36
QUOTE shift 37
subpattern shift 21
State 16:
pattern_declarations ::= PATTERN * subpattern
subpattern ::= * QUOTE
subpattern ::= * SUBPATTERN
subpattern ::= * subpattern QUOTE
subpattern ::= * subpattern SUBPATTERN
SUBPATTERN shift 36
QUOTE shift 37
subpattern shift 22
State 17:
lexfile ::= PHPCODE * declare rules
lexfile ::= PHPCODE * declare PHPCODE rules
declare ::= * COMMENTSTART declarations COMMENTEND
COMMENTSTART shift 8
declare shift 4
State 18:
rule ::= rule_subpattern * CODE
rule_subpattern ::= rule_subpattern * QUOTE
rule_subpattern ::= rule_subpattern * SUBPATTERN
SUBPATTERN shift 54
CODE shift 47
QUOTE shift 53
State 19:
rule ::= rule rule_subpattern * CODE
rule_subpattern ::= rule_subpattern * QUOTE
rule_subpattern ::= rule_subpattern * SUBPATTERN
SUBPATTERN shift 54
CODE shift 45
QUOTE shift 53
State 20:
processing_instructions ::= processing_instructions PI * SUBPATTERN
processing_instructions ::= processing_instructions PI * CODE
SUBPATTERN shift 44
CODE shift 40
State 21:
(12) pattern_declarations ::= pattern_declarations PATTERN subpattern *
subpattern ::= subpattern * QUOTE
subpattern ::= subpattern * SUBPATTERN
SUBPATTERN shift 38
QUOTE shift 41
{default} reduce 12
State 22:
(11) pattern_declarations ::= PATTERN subpattern *
subpattern ::= subpattern * QUOTE
subpattern ::= subpattern * SUBPATTERN
SUBPATTERN shift 38
QUOTE shift 41
{default} reduce 11
State 23:
processing_instructions ::= PI * SUBPATTERN
processing_instructions ::= PI * CODE
SUBPATTERN shift 42
CODE shift 39
State 24:
(18) rules ::= reset_rules PI SUBPATTERN rule COMMENTEND *
rules ::= reset_rules PI SUBPATTERN rule COMMENTEND * PHPCODE
PHPCODE shift 48
{default} reduce 18
State 25:
(3) lexfile ::= PHPCODE declare rules *
reset_rules ::= rules * COMMENTSTART
COMMENTSTART shift 43
{default} reduce 3
State 26:
(6) declarations ::= processing_instructions pattern_declarations *
pattern_declarations ::= pattern_declarations * PATTERN subpattern
PATTERN shift 15
{default} reduce 6
State 27:
(4) lexfile ::= PHPCODE declare PHPCODE rules *
reset_rules ::= rules * COMMENTSTART
COMMENTSTART shift 43
{default} reduce 4
State 28:
declare ::= COMMENTSTART declarations * COMMENTEND
COMMENTEND shift 55
State 29:
rules ::= reset_rules PI * SUBPATTERN rule COMMENTEND
rules ::= reset_rules PI * SUBPATTERN rule COMMENTEND PHPCODE
SUBPATTERN shift 5
State 30:
(14) rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND *
rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND * PHPCODE
PHPCODE shift 46
{default} reduce 14
State 31:
(2) lexfile ::= declare PHPCODE rules *
reset_rules ::= rules * COMMENTSTART
COMMENTSTART shift 43
{default} reduce 2
State 32:
(13) rules ::= COMMENTSTART rule COMMENTEND *
rules ::= COMMENTSTART rule COMMENTEND * PHPCODE
PHPCODE shift 56
{default} reduce 13
State 33:
(1) lexfile ::= declare rules *
reset_rules ::= rules * COMMENTSTART
COMMENTSTART shift 43
{default} reduce 1
State 34:
rules ::= COMMENTSTART PI * SUBPATTERN rule COMMENTEND
rules ::= COMMENTSTART PI * SUBPATTERN rule COMMENTEND PHPCODE
SUBPATTERN shift 3
State 35:
(17) rules ::= reset_rules rule COMMENTEND *
rules ::= reset_rules rule COMMENTEND * PHPCODE
PHPCODE shift 49
{default} reduce 17
State 36:
(29) subpattern ::= SUBPATTERN *
{default} reduce 29
State 37:
(28) subpattern ::= QUOTE *
{default} reduce 28
State 38:
(31) subpattern ::= subpattern SUBPATTERN *
{default} reduce 31
State 39:
(8) processing_instructions ::= PI CODE *
{default} reduce 8
State 40:
(10) processing_instructions ::= processing_instructions PI CODE *
{default} reduce 10
State 41:
(30) subpattern ::= subpattern QUOTE *
{default} reduce 30
State 42:
(7) processing_instructions ::= PI SUBPATTERN *
{default} reduce 7
State 43:
(21) reset_rules ::= rules COMMENTSTART *
{default} reduce 21
State 44:
(9) processing_instructions ::= processing_instructions PI SUBPATTERN *
{default} reduce 9
State 45:
(23) rule ::= rule rule_subpattern CODE *
{default} reduce 23
State 46:
(16) rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE *
{default} reduce 16
State 47:
(22) rule ::= rule_subpattern CODE *
{default} reduce 22
State 48:
(20) rules ::= reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE *
{default} reduce 20
State 49:
(19) rules ::= reset_rules rule COMMENTEND PHPCODE *
{default} reduce 19
State 50:
(25) rule_subpattern ::= SUBPATTERN *
{default} reduce 25
State 51:
(24) rule_subpattern ::= QUOTE *
{default} reduce 24
State 52:
(0) start ::= lexfile *
{default} reduce 0
State 53:
(26) rule_subpattern ::= rule_subpattern QUOTE *
{default} reduce 26
State 54:
(27) rule_subpattern ::= rule_subpattern SUBPATTERN *
{default} reduce 27
State 55:
(5) declare ::= COMMENTSTART declarations COMMENTEND *
{default} reduce 5
State 56:
(15) rules ::= COMMENTSTART rule COMMENTEND PHPCODE *
{default} reduce 15

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,795 @@
%name PHP_LexerGenerator_Parser
%declare_class {class PHP_LexerGenerator_Parser}
%include {
/* ?><?php {//*/
/**
* PHP_LexerGenerator, a php 5 lexer generator.
*
* This lexer generator translates a file in a format similar to
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
*
* PHP version 5
*
* LICENSE:
*
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* * Neither the name of the PHP_LexerGenerator nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category php
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Parser.y 246683 2007-11-22 04:43:52Z instance $
* @since File available since Release 0.1.0
*/
/**
* For regular expression validation
*/
require_once 'PHP/LexerGenerator/Regex/Lexer.php';
require_once 'PHP/LexerGenerator/Regex/Parser.php';
require_once 'PHP/LexerGenerator/Exception.php';
/**
* Token parser for plex files.
*
* This parser converts tokens pulled from {@link PHP_LexerGenerator_Lexer}
* into abstract patterns and rules, then creates the output file
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
* @copyright 2006 Gregory Beaver
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version @package_version@
* @since Class available since Release 0.1.0
*/
}
%syntax_error {
echo "Syntax Error on line " . $this->lex->line . ": token '" .
$this->lex->value . "' while parsing rule:";
foreach ($this->yystack as $entry) {
echo $this->tokenName($entry->major) . ' ';
}
foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
$expect[] = self::$yyTokenName[$token];
}
throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN
. '), expected one of: ' . implode(',', $expect));
}
%include_class {
private $patterns;
private $out;
private $lex;
private $input;
private $counter;
private $token;
private $value;
private $line;
private $matchlongest;
private $_regexLexer;
private $_regexParser;
private $_patternIndex = 0;
private $_outRuleIndex = 1;
private $caseinsensitive;
private $patternFlags;
private $unicode;
public $transTable = array(
1 => self::PHPCODE,
2 => self::COMMENTSTART,
3 => self::COMMENTEND,
4 => self::QUOTE,
5 => self::SINGLEQUOTE,
6 => self::PATTERN,
7 => self::CODE,
8 => self::SUBPATTERN,
9 => self::PI,
);
function __construct($outfile, $lex)
{
$this->out = fopen($outfile, 'wb');
if (!$this->out) {
throw new Exception('unable to open lexer output file "' . $outfile . '"');
}
$this->lex = $lex;
$this->_regexLexer = new PHP_LexerGenerator_Regex_Lexer('');
$this->_regexParser = new PHP_LexerGenerator_Regex_Parser($this->_regexLexer);
}
function doLongestMatch($rules, $statename, $ruleindex)
{
fwrite($this->out, '
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
return false; // end of input
}
do {
$rules = array(');
foreach ($rules as $rule) {
fwrite($this->out, '
\'/\G' . $rule['pattern'] . '/' . $this->patternFlags . ' \',');
}
fwrite($this->out, '
);
$match = false;
foreach ($rules as $index => $rule) {
if (preg_match($rule, substr(' . $this->input . ', ' .
$this->counter . '), $yymatches)) {
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception(\'Unexpected input at line \' . ' . $this->line . ' .
\': \' . ' . $this->input . '[' . $this->counter . ']);
}
' . $this->token . ' = $match[1];
' . $this->value . ' = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
if ($r === null) {
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
// accept this token
return true;
} elseif ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} elseif ($r === false) {
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
return false; // end of input
}
// skip this token
continue;
} else {');
fwrite($this->out, '
$yy_yymore_patterns = array_slice($rules, $this->token, true);
// yymore is needed
do {
if (!isset($yy_yymore_patterns[' . $this->token . '])) {
throw new Exception(\'cannot do yymore for the last token\');
}
$match = false;
foreach ($yy_yymore_patterns[' . $this->token . '] as $index => $rule) {
if (preg_match(\'/\' . $rule . \'/' . $this->patternFlags . '\',
' . $this->input . ', $yymatches, null, ' . $this->counter . ')) {
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
if ($match) {
if (strlen($yymatches[0]) > strlen($match[0][0])) {
$match = array($yymatches, $index); // matches, token
}
} else {
$match = array($yymatches, $index);
}
}
}
if (!$match) {
throw new Exception(\'Unexpected input at line \' . ' . $this->line . ' .
\': \' . ' . $this->input . '[' . $this->counter . ']);
}
' . $this->token . ' = $match[1];
' . $this->value . ' = $match[0][0];
$yysubmatches = $match[0];
array_shift($yysubmatches);
if (!$yysubmatches) {
$yysubmatches = array();
}
' . $this->line . ' = substr_count(' . $this->value . ', "\n");
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}();
} while ($r !== null || !$r);
if ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} else {
// accept
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
return true;
}
}
} while (true);
');
}
function doFirstMatch($rules, $statename, $ruleindex)
{
$patterns = array();
$pattern = '/';
$ruleMap = array();
$tokenindex = array();
$actualindex = 1;
$i = 0;
foreach ($rules as $rule) {
$ruleMap[$i++] = $actualindex;
$tokenindex[$actualindex] = $rule['subpatterns'];
$actualindex += $rule['subpatterns'] + 1;
$patterns[] = '\G(' . $rule['pattern'] . ')';
}
// Re-index tokencount from zero.
$tokencount = array_values($tokenindex);
$tokenindex = var_export($tokenindex, true);
$tokenindex = explode("\n", $tokenindex);
// indent for prettiness
$tokenindex = implode("\n ", $tokenindex);
$pattern .= implode('|', $patterns);
$pattern .= '/' . $this->patternFlags;
fwrite($this->out, '
$tokenMap = ' . $tokenindex . ';
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
return false; // end of input
}
');
fwrite($this->out, '$yy_global_pattern = \'' .
$pattern . '\';' . "\n");
fwrite($this->out, '
do {
if (preg_match($yy_global_pattern,' . $this->input . ', $yymatches, null, ' .
$this->counter .
')) {
$yysubmatches = $yymatches;
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
if (!count($yymatches)) {
throw new Exception(\'Error: lexing failed because a rule matched\' .
\' an empty string. Input "\' . substr(' . $this->input . ',
' . $this->counter . ', 5) . \'... state ' . $statename . '\');
}
next($yymatches); // skip global match
' . $this->token . ' = key($yymatches); // token number
if ($tokenMap[' . $this->token . ']) {
// extract sub-patterns for passing to lex function
$yysubmatches = array_slice($yysubmatches, ' . $this->token . ' + 1,
$tokenMap[' . $this->token . ']);
} else {
$yysubmatches = array();
}
' . $this->value . ' = current($yymatches); // token value
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
if ($r === null) {
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
// accept this token
return true;
} elseif ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} elseif ($r === false) {
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
return false; // end of input
}
// skip this token
continue;
} else {');
fwrite($this->out, '
$yy_yymore_patterns = array(' . "\n");
$extra = 0;
for($i = 0; count($patterns); $i++) {
unset($patterns[$i]);
$extra += $tokencount[0];
array_shift($tokencount);
fwrite($this->out, ' ' . $ruleMap[$i] . ' => array(' . $extra . ', "' .
implode('|', $patterns) . "\"),\n");
}
fwrite($this->out, ' );' . "\n");
fwrite($this->out, '
// yymore is needed
do {
if (!strlen($yy_yymore_patterns[' . $this->token . '][1])) {
throw new Exception(\'cannot do yymore for the last token\');
}
$yysubmatches = array();
if (preg_match(\'/\' . $yy_yymore_patterns[' . $this->token . '][1] . \'/' . $this->patternFlags . '\',
' . $this->input . ', $yymatches, null, ' . $this->counter .')) {
$yysubmatches = $yymatches;
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
next($yymatches); // skip global match
' . $this->token . ' += key($yymatches) + $yy_yymore_patterns[' . $this->token . '][0]; // token number
' . $this->value . ' = current($yymatches); // token value
' . $this->line . ' = substr_count(' . $this->value . ', "\n");
if ($tokenMap[' . $this->token . ']) {
// extract sub-patterns for passing to lex function
$yysubmatches = array_slice($yysubmatches, ' . $this->token . ' + 1,
$tokenMap[' . $this->token . ']);
} else {
$yysubmatches = array();
}
}
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
} while ($r !== null && !is_bool($r));
if ($r === true) {
// we have changed state
// process this token in the new state
return $this->yylex();
} elseif ($r === false) {
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
return false; // end of input
}
// skip this token
continue;
} else {
// accept
' . $this->counter . ' += strlen(' . $this->value . ');
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
return true;
}
}
} else {
throw new Exception(\'Unexpected input at line\' . ' . $this->line . ' .
\': \' . ' . $this->input . '[' . $this->counter . ']);
}
break;
} while (true);
');
}
function makeCaseInsensitve($string)
{
return preg_replace('/[a-z]/ie', "'[\\0'.strtoupper('\\0').']'", strtolower($string));
}
function outputRules($rules, $statename)
{
if (!$statename) {
$statename = $this -> _outRuleIndex;
}
fwrite($this->out, '
function yylex' . $this -> _outRuleIndex . '()
{');
if ($this->matchlongest) {
$ruleMap = array();
foreach ($rules as $i => $rule) {
$ruleMap[$i] = $i;
}
$this->doLongestMatch($rules, $statename, $this -> _outRuleIndex);
} else {
$ruleMap = array();
$actualindex = 1;
$i = 0;
foreach ($rules as $rule) {
$ruleMap[$i++] = $actualindex;
$actualindex += $rule['subpatterns'] + 1;
}
$this->doFirstMatch($rules, $statename, $this -> _outRuleIndex);
}
fwrite($this->out, '
} // end function
');
if (is_string($statename)) {
fwrite($this->out, '
const ' . $statename . ' = ' . $this -> _outRuleIndex . ';
');
}
foreach ($rules as $i => $rule) {
fwrite($this->out, ' function yy_r' . $this -> _outRuleIndex . '_' . $ruleMap[$i] . '($yy_subpatterns)
{
' . $rule['code'] .
' }
');
}
$this -> _outRuleIndex++; // for next set of rules
}
function error($msg)
{
echo 'Error on line ' . $this->lex->line . ': ' , $msg;
}
function _validatePattern($pattern, $update = false)
{
$this->_regexLexer->reset($pattern, $this->lex->line);
$this->_regexParser->reset($this->_patternIndex, $update);
try {
while ($this->_regexLexer->yylex()) {
$this->_regexParser->doParse(
$this->_regexLexer->token, $this->_regexLexer->value);
}
$this->_regexParser->doParse(0, 0);
} catch (PHP_LexerGenerator_Exception $e) {
$this->error($e->getMessage());
throw new PHP_LexerGenerator_Exception('Invalid pattern "' . $pattern . '"');
}
return $this->_regexParser->result;
}
}
start ::= lexfile.
lexfile ::= declare rules(B). {
fwrite($this->out, '
private $_yy_state = 1;
private $_yy_stack = array();
function yylex()
{
return $this->{\'yylex\' . $this->_yy_state}();
}
function yypushstate($state)
{
array_push($this->_yy_stack, $this->_yy_state);
$this->_yy_state = $state;
}
function yypopstate()
{
$this->_yy_state = array_pop($this->_yy_stack);
}
function yybegin($state)
{
$this->_yy_state = $state;
}
');
foreach (B as $rule) {
$this->outputRules($rule['rules'], $rule['statename']);
if ($rule['code']) {
fwrite($this->out, $rule['code']);
}
}
}
lexfile ::= declare(D) PHPCODE(B) rules(C). {
fwrite($this->out, '
private $_yy_state = 1;
private $_yy_stack = array();
function yylex()
{
return $this->{\'yylex\' . $this->_yy_state}();
}
function yypushstate($state)
{
array_push($this->_yy_stack, $this->_yy_state);
$this->_yy_state = $state;
}
function yypopstate()
{
$this->_yy_state = array_pop($this->_yy_stack);
}
function yybegin($state)
{
$this->_yy_state = $state;
}
');
if (strlen(B)) {
fwrite($this->out, B);
}
foreach (C as $rule) {
$this->outputRules($rule['rules'], $rule['statename']);
if ($rule['code']) {
fwrite($this->out, $rule['code']);
}
}
}
lexfile ::= PHPCODE(B) declare(D) rules(C). {
if (strlen(B)) {
fwrite($this->out, B);
}
fwrite($this->out, '
private $_yy_state = 1;
private $_yy_stack = array();
function yylex()
{
return $this->{\'yylex\' . $this->_yy_state}();
}
function yypushstate($state)
{
array_push($this->_yy_stack, $this->_yy_state);
$this->_yy_state = $state;
}
function yypopstate()
{
$this->_yy_state = array_pop($this->_yy_stack);
}
function yybegin($state)
{
$this->_yy_state = $state;
}
');
foreach (C as $rule) {
$this->outputRules($rule['rules'], $rule['statename']);
if ($rule['code']) {
fwrite($this->out, $rule['code']);
}
}
}
lexfile ::= PHPCODE(A) declare(D) PHPCODE(B) rules(C). {
if (strlen(A)) {
fwrite($this->out, A);
}
fwrite($this->out, '
private $_yy_state = 1;
private $_yy_stack = array();
function yylex()
{
return $this->{\'yylex\' . $this->_yy_state}();
}
function yypushstate($state)
{
array_push($this->_yy_stack, $this->_yy_state);
$this->_yy_state = $state;
}
function yypopstate()
{
$this->_yy_state = array_pop($this->_yy_stack);
}
function yybegin($state)
{
$this->_yy_state = $state;
}
');
if (strlen(B)) {
fwrite($this->out, B);
}
foreach (C as $rule) {
$this->outputRules($rule['rules'], $rule['statename']);
if ($rule['code']) {
fwrite($this->out, $rule['code']);
}
}
}
declare(A) ::= COMMENTSTART declarations(B) COMMENTEND. {
A = B;
$this->patterns = B['patterns'];
$this->_patternIndex = 1;
}
declarations(A) ::= processing_instructions(B) pattern_declarations(C). {
$expected = array(
'counter' => true,
'input' => true,
'token' => true,
'value' => true,
'line' => true,
);
foreach (B as $pi) {
if (isset($expected[$pi['pi']])) {
unset($expected[$pi['pi']]);
continue;
}
if (count($expected)) {
throw new Exception('Processing Instructions "' .
implode(', ', array_keys($expected)) . '" must be defined');
}
}
$expected = array(
'caseinsensitive' => true,
'counter' => true,
'input' => true,
'token' => true,
'value' => true,
'line' => true,
'matchlongest' => true,
'unicode' => true,
);
foreach (B as $pi) {
if (isset($expected[$pi['pi']])) {
$this->{$pi['pi']} = $pi['definition'];
if ($pi['pi'] == 'matchlongest') {
$this->matchlongest = true;
}
continue;
}
$this->error('Unknown processing instruction %' . $pi['pi'] .
', should be one of "' . implode(', ', array_keys($expected)) . '"');
}
$this->patternFlags = ($this->caseinsensitive ? 'i' : '')
. ($this->unicode ? 'u' : '');
A = array('patterns' => C, 'pis' => B);
$this->_patternIndex = 1;
}
processing_instructions(A) ::= PI(B) SUBPATTERN(C). {
A = array(array('pi' => B, 'definition' => C));
}
processing_instructions(A) ::= PI(B) CODE(C). {
A = array(array('pi' => B, 'definition' => C));
}
processing_instructions(A) ::= processing_instructions(P) PI(B) SUBPATTERN(C). {
A = P;
A[] = array('pi' => B, 'definition' => C);
}
processing_instructions(A) ::= processing_instructions(P) PI(B) CODE(C). {
A = P;
A[] = array('pi' => B, 'definition' => C);
}
pattern_declarations(A) ::= PATTERN(B) subpattern(C). {
A = array(B => C);
// reset internal indicator of where we are in a pattern
$this->_patternIndex = 0;
}
pattern_declarations(A) ::= pattern_declarations(B) PATTERN(C) subpattern(D). {
A = B;
if (isset(A[C])) {
throw new Exception('Pattern "' . C . '" is already defined as "' .
A[C] . '", cannot redefine as "' . D->string . '"');
}
A[C] = D;
// reset internal indicator of where we are in a pattern declaration
$this->_patternIndex = 0;
}
rules(A) ::= COMMENTSTART rule(B) COMMENTEND. {
A = array(array('rules' => B, 'code' => '', 'statename' => ''));
}
rules(A) ::= COMMENTSTART PI(P) SUBPATTERN(S) rule(B) COMMENTEND. {
if (P != 'statename') {
throw new Exception('Error: only %statename processing instruction ' .
'is allowed in rule sections (found ' . P . ').');
}
A = array(array('rules' => B, 'code' => '', 'statename' => S));
}
rules(A) ::= COMMENTSTART rule(B) COMMENTEND PHPCODE(C). {
A = array(array('rules' => B, 'code' => C, 'statename' => ''));
}
rules(A) ::= COMMENTSTART PI(P) SUBPATTERN(S) rule(B) COMMENTEND PHPCODE(C). {
if (P != 'statename') {
throw new Exception('Error: only %statename processing instruction ' .
'is allowed in rule sections (found ' . P . ').');
}
A = array(array('rules' => B, 'code' => C, 'statename' => S));
$this->_patternIndex = 1;
}
rules(A) ::= reset_rules(R) rule(B) COMMENTEND. {
A = R;
A[] = array('rules' => B, 'code' => '', 'statename' => '');
$this->_patternIndex = 1;
}
rules(A) ::= reset_rules(R) PI(P) SUBPATTERN(S) rule(B) COMMENTEND. {
if (P != 'statename') {
throw new Exception('Error: only %statename processing instruction ' .
'is allowed in rule sections (found ' . P . ').');
}
A = R;
A[] = array('rules' => B, 'code' => '', 'statename' => S);
}
rules(A) ::= reset_rules(R) rule(B) COMMENTEND PHPCODE(C). {
A = R;
A[] = array('rules' => B, 'code' => C, 'statename' => '');
}
rules(A) ::= reset_rules(R) PI(P) SUBPATTERN(S) rule(B) COMMENTEND PHPCODE(C). {
if (P != 'statename') {
throw new Exception('Error: only %statename processing instruction ' .
'is allowed in rule sections (found ' . P . ').');
}
A = R;
A[] = array('rules' => B, 'code' => C, 'statename' => S);
}
reset_rules(A) ::= rules(R) COMMENTSTART. {
A = R;
$this->_patternIndex = 1;
}
rule(A) ::= rule_subpattern(B) CODE(C). {
$name = B[1];
B = B[0];
B = $this->_validatePattern(B);
$this->_patternIndex += B['subpatterns'] + 1;
if (@preg_match('/' . str_replace('/', '\\/', B['pattern']) . '/', '')) {
$this->error('Rule "' . $name . '" can match the empty string, this will break lexing');
}
A = array(array('pattern' => str_replace('/', '\\/', B->string), 'code' => C, 'subpatterns' => B['subpatterns']));
}
rule(A) ::= rule(R) rule_subpattern(B) CODE(C).{
A = R;
$name = B[1];
B = B[0];
B = $this->_validatePattern(B);
$this->_patternIndex += B['subpatterns'] + 1;
if (@preg_match('/' . str_replace('/', '\\/', B['pattern']) . '/', '')) {
$this->error('Rule "' . $name . '" can match the empty string, this will break lexing');
}
A[] = array('pattern' => str_replace('/', '\\/', B->string), 'code' => C, 'subpatterns' => B['subpatterns']);
}
rule_subpattern(A) ::= QUOTE(B). {
A = array(preg_quote(B, '/'), B);
}
rule_subpattern(A) ::= SINGLEQUOTE(B). {
A = array($this->makeCaseInsensitve(preg_quote(B, '/')), B);
}
rule_subpattern(A) ::= SUBPATTERN(B). {
if (!isset($this->patterns[B])) {
$this->error('Undefined pattern "' . B . '" used in rules');
throw new Exception('Undefined pattern "' . B . '" used in rules');
}
A = array($this->patterns[B], B);
}
rule_subpattern(A) ::= rule_subpattern(B) QUOTE(C). {
A = array(B[0] . preg_quote(C, '/'), B[1] . ' ' . C);
}
rule_subpattern(A) ::= rule_subpattern(B) SINGLEQUOTE(C). {
A = array(B[0] . $this->makeCaseInsensitve(preg_quote(C, '/')), B[1] . ' ' . C);
}
rule_subpattern(A) ::= rule_subpattern(B) SUBPATTERN(C). {
if (!isset($this->patterns[C])) {
$this->error('Undefined pattern "' . C . '" used in rules');
throw new Exception('Undefined pattern "' . C . '" used in rules');
}
A = array(B[0] . $this->patterns[C], B[1] . ' ' . C);
}
subpattern(A) ::= QUOTE(B). {
A = preg_quote(B, '/');
}
subpattern(A) ::= SINGLEQUOTE(B). {
A = $this->makeCaseInsensitve(preg_quote(B, '/'));
}
subpattern(A) ::= SUBPATTERN(B). {
// increment internal sub-pattern counter
// adjust back-references in pattern based on previous pattern
$test = $this->_validatePattern(B, true);
$this->_patternIndex += $test['subpatterns'];
A = $test['pattern'];
}
subpattern(A) ::= subpattern(B) QUOTE(C). {
A = B . preg_quote(C, '/');
}
subpattern(A) ::= subpattern(B) SINGLEQUOTE(C). {
A = B . $this->makeCaseInsensitve(preg_quote(C, '/'));
}
subpattern(A) ::= subpattern(B) SUBPATTERN(C). {
// increment internal sub-pattern counter
// adjust back-references in pattern based on previous pattern
$test = $this->_validatePattern(C, true);
$this->_patternIndex += $test['subpatterns'];
A = B . $test['pattern'];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,285 @@
<?php
require_once 'PHP/LexerGenerator/Regex/Parser.php';
class PHP_LexerGenerator_Regex_Lexer
{
const MATCHSTART = PHP_LexerGenerator_Regex_Parser::MATCHSTART;
const MATCHEND = PHP_LexerGenerator_Regex_Parser::MATCHEND;
const CONTROLCHAR = PHP_LexerGenerator_Regex_Parser::CONTROLCHAR;
const OPENCHARCLASS = PHP_LexerGenerator_Regex_Parser::OPENCHARCLASS;
const FULLSTOP = PHP_LexerGenerator_Regex_Parser::FULLSTOP;
const TEXT = PHP_LexerGenerator_Regex_Parser::TEXT;
const BACKREFERENCE = PHP_LexerGenerator_Regex_Parser::BACKREFERENCE;
const OPENASSERTION = PHP_LexerGenerator_Regex_Parser::OPENASSERTION;
const COULDBEBACKREF = PHP_LexerGenerator_Regex_Parser::COULDBEBACKREF;
const NEGATE = PHP_LexerGenerator_Regex_Parser::NEGATE;
const HYPHEN = PHP_LexerGenerator_Regex_Parser::HYPHEN;
const CLOSECHARCLASS = PHP_LexerGenerator_Regex_Parser::CLOSECHARCLASS;
const BAR = PHP_LexerGenerator_Regex_Parser::BAR;
const MULTIPLIER = PHP_LexerGenerator_Regex_Parser::MULTIPLIER;
const INTERNALOPTIONS = PHP_LexerGenerator_Regex_Parser::INTERNALOPTIONS;
const COLON = PHP_LexerGenerator_Regex_Parser::COLON;
const OPENPAREN = PHP_LexerGenerator_Regex_Parser::OPENPAREN;
const CLOSEPAREN = PHP_LexerGenerator_Regex_Parser::CLOSEPAREN;
const PATTERNNAME = PHP_LexerGenerator_Regex_Parser::PATTERNNAME;
const POSITIVELOOKBEHIND = PHP_LexerGenerator_Regex_Parser::POSITIVELOOKBEHIND;
const NEGATIVELOOKBEHIND = PHP_LexerGenerator_Regex_Parser::NEGATIVELOOKBEHIND;
const POSITIVELOOKAHEAD = PHP_LexerGenerator_Regex_Parser::POSITIVELOOKAHEAD;
const NEGATIVELOOKAHEAD = PHP_LexerGenerator_Regex_Parser::NEGATIVELOOKAHEAD;
const ONCEONLY = PHP_LexerGenerator_Regex_Parser::ONCEONLY;
const COMMENT = PHP_LexerGenerator_Regex_Parser::COMMENT;
const RECUR = PHP_LexerGenerator_Regex_Parser::RECUR;
const ESCAPEDBACKSLASH = PHP_LexerGenerator_Regex_Parser::ESCAPEDBACKSLASH;
private $input;
private $N;
public $token;
public $value;
public $line;
function __construct($data)
{
$this->input = $data;
$this->N = 0;
}
function reset($data, $line)
{
$this->input = $data;
$this->N = 0;
// passed in from parent parser
$this->line = $line;
$this->yybegin(self::INITIAL);
}
/*!lex2php
%input {$this->input}
%counter {$this->N}
%token {$this->token}
%value {$this->value}
%line {$this->line}
NONESCAPE = /[^[\\^$.|()?*+{}]+/
NONESCAPECHARCLASS = /[^\-\\]/
ESCAPEDTHING = /\\[][{}*.^$|?()+]/
ESCAPEDCHARCLASSTHING = /\\[]\.\-\^]/
MULTIPLIER = /\*\?|\+\?|[*?+]|\{[0-9]+\}|\{[0-9]+,\}|\{[0-9]+,[0-9]+\}/
STRINGCHAR = /\\[frnt]|\\x[0-9a-fA-F][0-9a-fA-F]?|\\[0-7][0-7][0-7]|\\x\{[0-9a-fA-F]+\}/
CONTROLCHAR = /\\[abBGcedDsSwW0C]|\\c\\/
COULDBEBACKREF = /\\[0-9][0-9]/
CHARCLASSCONTROLCHAR = /\\[bacedDsSwW0C]|\\c\\|\\x\{[0-9a-fA-F]+\}|\\[0-7][0-7][0-7]|\\x[0-9a-fA-F][0-9a-fA-F]?/
SUBJECTEND = /\\[zZ]/
BACKREF = /\\[1-9]/
UNICODESTUFF = /\\p\{\^?..?\}|\\P\{..?\}|\\X/
PROPERTYCODES = /C[cfnos]?|L[lmotu]?|M[cen]?|N[dlo]?|P[cdefios]?|S[ckmo]?|Z[lps]?/
SIMPLEPROPERTYCODES = /[CLMNPSZ]/
INTERNALOPTIONS = /[imsxUX]+-[imsxUX]+|[imsxUX]+|-[imsxUX]+/
ANYTHING = /./
PATTERNNAME = /[^>]+/
COMMENT = /#[^)]+/
HYPHEN = /-(?!])/
*/
/*!lex2php
%statename INITIAL
"\\\\" {
$this->token = self::ESCAPEDBACKSLASH;
}
NONESCAPE {
$this->token = self::TEXT;
}
ESCAPEDTHING {
$this->token = self::CONTROLCHAR;
}
"[" {
$this->token = self::OPENCHARCLASS;
$this->yybegin(self::CHARACTERCLASSSTART);
}
"|" {
$this->token = self::BAR;
}
STRINGCHAR {
$this->token = self::TEXT;
}
COULDBEBACKREF {
$this->token = self::COULDBEBACKREF;
}
CONTROLCHAR {
$this->token = self::CONTROLCHAR;
}
"^" {
$this->token = self::MATCHSTART;
}
"\\A" {
$this->token = self::MATCHSTART;
}
")" {
$this->token = self::CLOSEPAREN;
$this->yybegin(self::INITIAL);
}
"$" {
$this->token = self::MATCHEND;
}
MULTIPLIER {
$this->token = self::MULTIPLIER;
}
SUBJECTEND {
$this->token = self::MATCHEND;
}
"(?" {
$this->token = self::OPENASSERTION;
$this->yybegin(self::ASSERTION);
}
"(" {
$this->token = self::OPENPAREN;
}
"." {
$this->token = self::FULLSTOP;
}
BACKREF {
$this->token = self::BACKREFERENCE;
}
UNICODESTUFF {
$this->token = self::CONTROLCHAR;
}
"\\p{" PROPERTYCODES "}" {
$this->token = self::CONTROLCHAR;
}
"\\p{^" PROPERTYCODES "}" {
$this->token = self::CONTROLCHAR;
}
"\\p" SIMPLEPROPERTYCODES {
$this->token = self::CONTROLCHAR;
}
"\\" {
return false;
}
*/
/*!lex2php
%statename CHARACTERCLASSSTART
"^" {
$this->token = self::NEGATE;
}
"]" {
$this->yybegin(self::CHARACTERCLASS);
$this->token = self::TEXT;
}
ANYTHING {
$this->yybegin(self::CHARACTERCLASS);
return true;
}
*/
/*!lex2php
%statename CHARACTERCLASS
"\\\\" {
$this->token = self::ESCAPEDBACKSLASH;
}
"]" {
$this->yybegin(self::INITIAL);
$this->token = self::CLOSECHARCLASS;
}
STRINGCHAR {
$this->token = self::TEXT;
}
CHARCLASSCONTROLCHAR {
$this->token = self::TEXT;
}
COULDBEBACKREF {
$this->token = self::COULDBEBACKREF;
}
BACKREF {
$this->token = self::BACKREFERENCE;
}
ESCAPEDCHARCLASSTHING {
$this->token = self::TEXT;
}
HYPHEN {
$this->token = self::HYPHEN;
$this->yybegin(self::RANGE);
}
NONESCAPECHARCLASS {
$this->token = self::TEXT;
}
"\\" {
return false; // ignore escaping of normal text
}
ANYTHING {
$this->token = self::TEXT;
}
*/
/*!lex2php
%statename RANGE
"\\\\" {
$this->token = self::ESCAPEDBACKSLASH;
}
"\\]" {
$this->token = self::TEXT;
$this->yybegin(self::CHARACTERCLASS);
}
CHARCLASSCONTROLCHAR {
$this->token = self::TEXT;
$this->yybegin(self::CHARACTERCLASS);
}
COULDBEBACKREF {
$this->token = self::COULDBEBACKREF;
}
BACKREF {
$this->token = self::BACKREFERENCE;
}
NONESCAPECHARCLASS {
$this->token = self::TEXT;
$this->yybegin(self::CHARACTERCLASS);
}
"\\" {
return false; // ignore escaping of normal text
}
*/
/*!lex2php
%statename ASSERTION
INTERNALOPTIONS {
$this->token = self::INTERNALOPTIONS;
}
":" {
$this->token = self::COLON;
$this->yybegin(self::INITIAL);
}
")" {
$this->token = self::CLOSEPAREN;
$this->yybegin(self::INITIAL);
}
"P<" PATTERNNAME ">" {
$this->token = self::PATTERNNAME;
$this->yybegin(self::INITIAL);
}
"<=" {
$this->token = self::POSITIVELOOKBEHIND;
$this->yybegin(self::INITIAL);
}
"<!" {
$this->token = self::NEGATIVELOOKBEHIND;
$this->yybegin(self::INITIAL);
}
"=" {
$this->token = self::POSITIVELOOKAHEAD;
$this->yybegin(self::INITIAL);
}
"!" {
$this->token = self::NEGATIVELOOKAHEAD;
$this->yybegin(self::INITIAL);
}
">" {
$this->token = self::ONCEONLY;
$this->yybegin(self::INITIAL);
}
"(?" {
$this->token = self::OPENASSERTION;
}
COMMENT {
$this->token = self::COMMENT;
$this->yybegin(self::INITIAL);
}
"R" {
$this->token = self::RECUR;
}
ANYTHING {
$this->yybegin(self::INITIAL);
return true;
}
*/
}

File diff suppressed because it is too large Load Diff

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