1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22
23
24package installer::windows::feature;
25
26use installer::existence;
27use installer::exiter;
28use installer::files;
29use installer::globals;
30use installer::sorter;
31use installer::worker;
32use installer::windows::idtglobal;
33use installer::windows::language;
34
35##############################################################
36# Returning the gid for a feature.
37# Attention: Maximum length
38##############################################################
39
40sub get_feature_gid
41{
42	my ($onefeature) = @_;
43
44	my $gid = "";
45
46	if ( $onefeature->{'gid'} ) { $gid = $onefeature->{'gid'}; }
47
48	# Attention: Maximum feature length is 38!
49	installer::windows::idtglobal::shorten_feature_gid(\$gid);
50
51	return $gid
52}
53
54##############################################################
55# Returning the gid of the parent.
56# Attention: Maximum length
57##############################################################
58
59sub get_feature_parent
60{
61	my ($onefeature) = @_;
62
63	my $parentgid = "";
64
65	if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; }
66
67	# The modules, hanging directly below the root, have to be root modules.
68	# Only then it is possible to make the "real" root module invisible by
69	# setting the display to "0".
70
71	if ( $parentgid eq $installer::globals::rootmodulegid ) { $parentgid = ""; }
72
73	# Attention: Maximum feature length is 38!
74	installer::windows::idtglobal::shorten_feature_gid(\$parentgid);
75
76	return $parentgid
77}
78
79##############################################################
80# Returning the display for a feature.
81# 0: Feature is not shown
82# odd: subfeatures are shown
83# even:  subfeatures are not shown
84##############################################################
85
86sub get_feature_display
87{
88	my ($onefeature) = @_;
89
90	my $display;
91	my $parentid = "";
92
93	if ( $onefeature->{'ParentID'} ) { $parentid = $onefeature->{'ParentID'}; }
94
95	if ( $parentid eq "" )
96	{
97		$display = "0";									# root module is not visible
98	}
99	elsif ( $onefeature->{'gid'} eq "gid_Module_Prg")	# program module shows subfeatures
100	{
101		$display = "1";									# root module shows subfeatures
102	}
103	else
104	{
105		$display = "2";									# all other modules do not show subfeatures
106	}
107
108	# special case: Feature has flag "HIDDEN_ROOT" -> $display is 0
109	my $styles = "";
110	if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; }
111	if ( $styles =~ /\bHIDDEN_ROOT\b/ ) { $display = "0"; }
112
113	# Special handling for language modules. Only visible in multilingual installation set
114	if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && ( ! $installer::globals::ismultilingual )) { $display = "0"; }
115
116	# Special handling for c05office. No program module visible.
117	if (( $onefeature->{'gid'} eq "gid_Module_Prg" ) && ( $installer::globals::product =~ /c05office/i )) { $display = "0";	}
118
119	# making all feature invisible in Language packs!
120	if ( $installer::globals::languagepack ) { $display = "0"; }
121
122	return $display
123}
124
125##############################################################
126# Returning the level for a feature.
127##############################################################
128
129sub get_feature_level
130{
131	my ($onefeature) = @_;
132
133	my $level = "20";	# the default
134
135	my $localdefault = "";
136
137	if ( $onefeature->{'Default'} ) { $localdefault = $onefeature->{'Default'}; }
138
139	if ( $localdefault eq "NO" )	# explicitely set Default = "NO"
140	{
141		$level = "200";				# deselected in default installation, base is 100
142		if ( $installer::globals::patch ) { $level = "20"; }
143	}
144
145	# special handling for Java and Ada
146	if ( $onefeature->{'Name'} )
147	{
148		if ( $onefeature->{'Name'} =~ /java/i ) { $level = $level + 40; }
149	}
150
151	# if FeatureLevel is defined in scp, this will be used
152
153	if ( $onefeature->{'FeatureLevel'} ) { $level = $onefeature->{'FeatureLevel'}; }
154
155	return $level
156}
157
158##############################################################
159# Returning the directory for a feature.
160##############################################################
161
162sub get_feature_directory
163{
164	my ($onefeature) = @_;
165
166	my $directory;
167
168	$directory = "INSTALLLOCATION";
169
170	return $directory
171}
172
173##############################################################
174# Returning the directory for a feature.
175##############################################################
176
177sub get_feature_attributes
178{
179	my ($onefeature) = @_;
180
181	my $attributes;
182
183	# No advertising of features and no leaving on network.
184	# Feature without parent must not have the "2"
185
186	my $parentgid = "";
187	if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; }
188
189	if (( $parentgid eq "" ) || ( $parentgid eq $installer::globals::rootmodulegid )) { $attributes = "8"; }
190	else { $attributes = "10"; }
191
192	return $attributes
193}
194
195#################################################################################
196# Replacing one variable in one files
197#################################################################################
198
199sub replace_one_variable
200{
201	my ($translationfile, $variable, $searchstring) = @_;
202
203	for ( my $i = 0; $i <= $#{$translationfile}; $i++ )
204	{
205		${$translationfile}[$i] =~ s/\%$searchstring/$variable/g;
206	}
207}
208
209#################################################################################
210# Replacing the variables in the feature names and descriptions
211#################################################################################
212
213sub replace_variables
214{
215	my ($translationfile, $variableshashref) = @_;
216
217	foreach $key (keys %{$variableshashref})
218	{
219		my $value = $variableshashref->{$key};
220		replace_one_variable($translationfile, $value, $key);
221	}
222}
223
224#################################################################################
225# Collecting the feature recursively.
226#################################################################################
227
228sub collect_modules_recursive
229{
230	my ($modulesref, $parentid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted) = @_;
231
232	my @allchildren = ();
233	my $childrenexist = 0;
234
235	# Collecting children from Module $parentid
236
237	my $modulegid;
238	foreach $modulegid ( keys %{$directparent})
239	{
240		if ( $directparent->{$modulegid} eq $parentid )
241		{
242			my %childhash = ( "gid" => "$modulegid", "Sortkey" => "$directsortkey->{$modulegid}");
243			push(@allchildren, \%childhash);
244			$childrenexist = 1;
245		}
246	}
247
248	# Sorting children
249
250	if ( $childrenexist )
251	{
252		# Sort children
253		installer::sorter::sort_array_of_hashes_numerically(\@allchildren, "Sortkey");
254
255		# Adding children to new array
256		my $childhashref;
257		foreach $childhashref ( @allchildren )
258		{
259			my $gid = $childhashref->{'gid'};
260
261			# Saving all lines, that have this 'gid'
262
263			my $unique;
264			foreach $unique ( keys %{$directgid} )
265			{
266				if ( $directgid->{$unique} eq $gid )
267				{
268					push(@{$feature}, ${$modulesref}[$directaccess->{$unique}]);
269					if ( $sorted->{$unique} == 1 ) { installer::exiter::exit_program("ERROR: Sorting feature failed! \"$unique\" already sorted.", "sort_feature"); }
270					$sorted->{$unique} = 1;
271				}
272			}
273
274			collect_modules_recursive($modulesref, $gid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted);
275		}
276	}
277}
278
279#################################################################################
280# Sorting the feature in specified order. Evaluated is the key "Sortkey", that
281# is set in scp2 projects.
282# The display order of modules in Windows Installer is dependent from the order
283# in the idt file. Therefore the order of the modules array has to be adapted
284# to the Sortkey order, before the idt file is created.
285#################################################################################
286
287sub sort_feature
288{
289	my ($modulesref) = @_;
290
291	my @feature = ();
292
293	my %directaccess = ();
294	my %directparent = ();
295	my %directgid = ();
296	my %directsortkey = ();
297	my %sorted = ();
298
299	for ( my $i = 0; $i <= $#{$modulesref}; $i++ )
300	{
301		my $onefeature = ${$modulesref}[$i];
302
303		my $uniquekey = $onefeature->{'uniquekey'};
304		my $modulegid = $onefeature->{'gid'};
305
306		$directaccess{$uniquekey} = $i;
307
308		$directgid{$uniquekey} = $onefeature->{'gid'};
309
310		# ParentID and Sortkey are not saved for the 'uniquekey', but only for the 'gid'
311
312		if ( $onefeature->{'ParentID'} ) { $directparent{$modulegid} = $onefeature->{'ParentID'}; }
313		else { $directparent{$modulegid} = ""; }
314
315		if ( $onefeature->{'Sortkey'} ) { $directsortkey{$modulegid} = $onefeature->{'Sortkey'}; }
316		else { $directsortkey{$modulegid} = "9999"; }
317
318		# Bookkeeping:
319		$sorted{$uniquekey} = 0;
320	}
321
322	# Searching all feature recursively, beginning with ParentID = ""
323	my $parentid = "";
324	collect_modules_recursive($modulesref, $parentid, \@feature, \%directaccess, \%directgid, \%directparent, \%directsortkey, \%sorted);
325
326	# Bookkeeping
327	my $modulekey;
328	foreach $modulekey ( keys %sorted )
329	{
330		if ( $sorted{$modulekey} == 0 )
331		{
332            $installer::logger::Lang->printf(
333                "Warning: Module \"%s\" could not be sorted. Added to the end of the module array.\n",
334                $modulekey);
335			push(@feature, ${$modulesref}[$directaccess{$modulekey}]);
336		}
337	}
338
339	return \@feature;
340}
341
342#################################################################################
343# Adding a unique key to the modules array. The gid is not unique for
344# multilingual modules. Only the combination from gid and specific language
345# is unique. Uniqueness is required for sorting mechanism.
346#################################################################################
347
348sub add_uniquekey
349{
350	my ( $modulesref ) = @_;
351
352	for ( my $i = 0; $i <= $#{$modulesref}; $i++ )
353	{
354		my $uniquekey = ${$modulesref}[$i]->{'gid'};
355		if ( ${$modulesref}[$i]->{'specificlanguage'} ) { $uniquekey = $uniquekey . "_" . ${$modulesref}[$i]->{'specificlanguage'}; }
356		${$modulesref}[$i]->{'uniquekey'} = $uniquekey;
357	}
358}
359
360#################################################################################
361# Creating the file Feature.idt dynamically
362# Content:
363# Feature Feature_Parent Title Description Display Level Directory_ Attributes
364#################################################################################
365
366sub prepare_feature_table ($$$)
367{
368	my ($modules, $language, $variables) = @_;
369
370    my $features = [];
371
372    foreach my $onefeature (@$modules)
373    {
374        # Java and Ada only, if the correct settings are set
375        my $styles = $onefeature->{'Styles'};
376        $styles = "" unless defined $styles;
377        if (( $styles =~ /\bJAVAMODULE\b/ ) && ( ! ($variables->{'JAVAPRODUCT'} ))) { next; }
378
379        # Controlling the language!
380        # Only language independent feature or feature with the correct language will be included into the table
381
382        next if $onefeature->{'ismultilingual'} && ($onefeature->{'specificlanguage'} ne $language);
383
384        my $feature_gid =get_feature_gid($onefeature);
385
386        my $feature = {
387            'Feature' => $feature_gid,
388            'Feature_Parent' => get_feature_parent($onefeature),
389            'Title' => $onefeature->{'Name'},
390            'Description' => $onefeature->{'Description'},
391            'Display' => get_feature_display($onefeature),
392            'Level' => get_feature_level($onefeature),
393            'Directory_' => get_feature_directory($onefeature),
394            'Attributes' => get_feature_attributes($onefeature)
395        };
396        push @$features, $feature;
397
398        # collecting all feature in global feature collector (so that properties can be set in property table)
399        $installer::globals::featurecollector{$feature_gid} = 1;
400
401        # collecting all language feature in feature collector for check of language selection
402        if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && $onefeature->{'ParentID'} ne $installer::globals::rootmodulegid)
403        {
404            $installer::globals::multilingual_only_modules{$feature_gid} = 1;
405        }
406
407        # collecting all application feature in global feature collector for check of application selection
408        if ( $styles =~ /\bAPPLICATIONMODULE\b/ )
409        {
410            $installer::globals::application_modules{$feature_gid} = 1;
411        }
412    }
413
414    return $features;
415}
416
417
418
419
420=head add_missing_features($features)
421
422    When we are building a release, then there may be features missing
423    that where present in the source release.  As missing features
424    would prevent patches from being created, we add the missing
425    features.
426
427    The returned feature hash is either identical to the given
428    $features or is a copy with the missing features added.
429
430=cut
431
432sub add_missing_features ($)
433{
434    my ($features) = @_;
435
436    return $features if ! $installer::globals::is_release;
437
438    # Aquire the feature list of the source release.
439    my $source_feature_table = $installer::globals::source_msi->GetTable("Feature");
440    my $feature_column_index = $source_feature_table->GetColumnIndex("Feature");
441
442    # Prepare fast lookup of the target features.
443    my %target_feature_map = map {$_->{'Feature'} => $_} @$features;
444
445    # Find missing features.
446    my @missing_features = ();
447    foreach my $source_feature_row (@{$source_feature_table->GetAllRows()})
448    {
449        my $feature_gid = $source_feature_row->GetValue($feature_column_index);
450        if ( ! defined $target_feature_map{$feature_gid})
451        {
452            push @missing_features, $source_feature_row;
453        }
454    }
455
456    # Return when there are no missing features.
457    return $features if scalar @missing_features==0;
458
459    # Process the missing features.
460    my $extended_features = [@$features];
461    foreach my $missing_feature_row (@missing_features)
462    {
463        my %feature = map
464            {$_ => $missing_feature_row->GetValue($_)}
465            ('Feature', 'Feature_Parent', 'Title', 'Description', 'Display', 'Level', 'Directory_', 'Attributes');
466        push @$extended_features, \%feature;
467
468        $installer::logger::Lang->printf("added missing feature %s\n", $feature->{'Feature'});
469    }
470    return $extended_features;
471}
472
473
474
475
476sub create_feature_table ($$$)
477{
478	my ($basedir, $language, $features) = @_;
479
480    my @feature_table = ();
481    installer::windows::idtglobal::write_idt_header(\@feature_table, "feature");
482
483    foreach my $feature (@$features)
484    {
485        my $line = join("\t",
486            $feature->{'Feature'},
487            $feature->{'Feature_Parent'},
488            $feature->{'Title'},
489            $feature->{'Description'},
490            $feature->{'Display'},
491            $feature->{'Level'},
492            $feature->{'Directory_'},
493            $feature->{'Attributes'}) . "\n";
494
495        push(@feature_table, $line);
496    }
497
498    my $filename = $basedir . $installer::globals::separator . "Feature.idt" . "." . $language;
499    installer::files::save_file($filename ,\@feature_table);
500    $installer::logger::Lang->printf("Created idt file: %s\n", $filename);
501}
502
5031;
504