Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

modules/Studio/parsers/StudioParser.php (18 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
42
43
44
45
46
/**
47
 * interface for studio parsers
48
 */
49
50
class StudioParser {
51
	var $positions = array ();
52
	var $rows = array ();
53
	var $cols = array ();
54
	var $curFile = '';
55
	var $curText = '';
56
	var $form;
57
	var $labelEditor = true;
58
	var $curType = 'detail';
59
	var $fieldEditor = true;
60
	var $oldMatches = array();
61
62
	function getFileType($type, $setType=true){
63
		switch($type){
64
			case 'EditView':$type = 'edit'; break;
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
65
			case 'SearchForm': $type= 'search';break;
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
66
			case 'ListView': $type= 'list';break;
0 ignored issues
show
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
67
			default: $type= 'detail';
0 ignored issues
show
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
68
		}
69
70
		if($setType){
71
			$this->curType = $type;
72
		}
73
		return $type;
74
	}
75
76
	function getParsers($file){
77
		if(substr_count($file, 'DetailView.html') > 0 || substr_count($file, 'EditView.html' ) > 0) return array('default'=>'StudioParser', array('StudioParser', 'StudioRowParser'));
78
		if(substr_count($file, 'ListView.html' ) > 0) return array('default'=>'XTPLListViewParser', array('XTPLListViewParser'));
79
		return array('default'=>'StudioParser', array('StudioParser'));
80
	}
81
82
83
	function parseRows($str){
84
		preg_match_all("'(<tr[^>]*)>(.*?)(</tr[^>]*>)'si", $str, $this->rows,PREG_SET_ORDER);
85
86
	}
87
88
	function parseNames($str){
89
		$results = array();
90
		preg_match_all("'name[ ]*=[ ]*[\'\"]+([a-zA-Z0-9\_]+)[\'\"]+'si", $str, $results,PREG_SET_ORDER);
91
		return $results;
92
	}
93
94
	function parseLabels($str){
95
		$mod = array();
96
		$app = array();
97
		preg_match_all("'\{MOD\.([a-zA-Z0-9\_]+)\}'si", $str, $mod,PREG_SET_ORDER);
98
		preg_match_all("'\{APP\.([a-zA-Z0-9\_]+)\}'si", $str, $app,PREG_SET_ORDER);
99
		return array_merge($app, $mod);
100
	}
101
102
	function getMaxPosition(){
103
		$max = 0;
104
		for($i = 0; $i < count($this->positions) ; $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
105
			if($this->positions[$i][2] >= $max){
106
				$max = $this->positions[$i][2] + 1;
107
			}
108
		}
109
		return $max;
110
	}
111
	function parsePositions($str, $output= false) {
112
		$results = array();
113
		preg_match_all("'<span[^>]*sugar=[\'\"]+([a-zA-Z\_]*)([0-9]+)([b]*)[\'\"]+[^>]*>(.*?)</span[ ]*sugar=[\'\"]+[a-zA-Z0-9\_]*[\'\"]+>'si", $str, $results, PREG_SET_ORDER);
114
		if($output){
115
			return $results;
116
		}
117
		$this->positions = $results;
0 ignored issues
show
Documentation Bug introduced by
It seems like $results can be null. However, the property $positions is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
118
	}
119
	function parseCols($str){
120
		preg_match_all("'(<td[^>]*?)>(.*?)(</td[^>]*?>)'si", $str, $this->cols,PREG_SET_ORDER);
121
122
	}
123
	function parse($str){
124
		$this->parsePositions($str);
125
	}
126
	function positionCount($str) {
127
		$result = array ();
128
		return preg_match_all("'<span[^>]*sugar=[\'\"]+([a-zA-Z\_]*)([0-9]+)([b]*)[\'\"]+[^>]*>(.*?)</span[ ]*sugar=[\'\"]+[a-zA-Z0-9\_]*[\'\"]+>'si", $str, $result, PREG_SET_ORDER)/2;
129
	}
130
	function rowCount($str) {
131
		$result = array ();
132
		return preg_match_all("'(<tr[^>]*>)(.*?)(</tr[^>]*>)'si", $str, $result);
133
	}
134
135
	function loadFile($file) {
136
		$this->curFile = $file;
137
		$this->curText = file_get_contents($file);
138
		$this->form = <<<EOQ
139
		</form>
140
		<form name='studio'  method='POST'>
141
			<input type='hidden' name='action' value='save'>
142
			<input type='hidden' name='module' value='Studio'>
143
144
EOQ;
145
146
	}
147
	function buildImageButtons($buttons,$horizontal=true){
148
		$text = '<table cellspacing=2><tr>';
149
		foreach($buttons as $button){
150
			if(!$horizontal){
151
				$text .= '</tr><tr>';
152
			}
153
			if(!empty($button['plain'])){
154
				$text .= <<<EOQ
155
				<td valign='center' {$button['actionScript']}>
156
EOQ;
157
158
			}else{
159
160
161
				$text .= <<<EOQ
162
				<td valign='center' class='button' style='cursor:default' onmousedown='this.className="buttonOn";return false;' onmouseup='this.className="button"' onmouseout='this.className="button"' {$button['actionScript']} >
163
EOQ;
164
			}
165
            if ( !isset($button['image']) )
166
                $text .= "{$button['text']}</td>";
167
            else
168
			    $text .= "{$button['image']}&nbsp;{$button['text']}</td>";
169
		}
170
		$text .= '</tr></table>';
171
		return $text;
172
	}
173
174
	function generateButtons(){
175
176
        global $mod_strings;
177
		$imageSave = SugarThemeRegistry::current()->getImage( 'studio_save', '',null,null,'.gif',$mod_strings['LBL_SAVE']);
178
		$imagePublish = SugarThemeRegistry::current()->getImage( 'studio_publish', '',null,null,'.gif',$mod_strings['LBL_PUBLISH']);
179
		$imageHistory = SugarThemeRegistry::current()->getImage( 'studio_history', '',null,null,'.gif',$mod_strings['LBL_HISTORY']);
180
		$imageAddRows = SugarThemeRegistry::current()->getImage('studio_addRows', '',null,null,'.gif',$mod_strings['LBL_ADDROWS']);
181
		$imageUndo = SugarThemeRegistry::current()->getImage('studio_undo', '',null,null,'.gif',$mod_strings['LBL_UNDO']);
182
		$imageRedo = SugarThemeRegistry::current()->getImage('studio_redo', '',null,null,'.gif',$mod_strings['LBL_REDO']);
183
		$imageAddField = SugarThemeRegistry::current()->getImage( 'studio_addField', '',null,null,'.gif',$mod_strings['LBL_ADDFIELD']);
184
		$buttons = array();
185
186
		$buttons[] = array('image'=>$imageUndo,'text'=>$GLOBALS['mod_strings']['LBL_BTN_UNDO'],'actionScript'=>"onclick='jstransaction.undo()'" );
187
		$buttons[] = array('image'=>$imageRedo,'text'=>$GLOBALS['mod_strings']['LBL_BTN_REDO'],'actionScript'=>"onclick='jstransaction.redo()'" );
188
		$buttons[] = array('image'=>$imageAddField,'text'=>$GLOBALS['mod_strings']['LBL_BTN_ADDCUSTOMFIELD'],'actionScript'=>"onclick='studiopopup.display();return false;'" );
189
		$buttons[] = array('image'=>$imageAddRows,'text'=>$GLOBALS['mod_strings']['LBL_BTN_ADDROWS'],'actionScript'=>"onclick='if(!confirmNoSave())return false;document.location.href=\"index.php?module=Studio&action=EditLayout&parser=StudioRowParser\"'" ,);
190
		$buttons[] = array('image'=>$imageAddRows,'text'=>$GLOBALS['mod_strings']['LBL_BTN_TABINDEX'],'actionScript'=>"onclick='if(!confirmNoSave())return false;document.location.href=\"index.php?module=Studio&action=EditLayout&parser=TabIndexParser\"'" ,);
191
		$buttons[] = array('image'=>'', 'text'=>'-', 'actionScript'=>'', 'plain'=>true);
192
193
		$buttons[] = array('image'=>$imageSave,'text'=>$GLOBALS['mod_strings']['LBL_BTN_SAVE'],'actionScript'=>"onclick='studiojs.save(\"studio\", false);'");
194
		$buttons[] = array('image'=>$imagePublish,'text'=>$GLOBALS['mod_strings']['LBL_BTN_SAVEPUBLISH'],'actionScript'=>"onclick='studiojs.save(\"studio\", true);'");
195
		$buttons[] = array('image'=>$imageHistory,'text'=>$GLOBALS['mod_strings']['LBL_BTN_HISTORY'],'actionScript'=>"onclick='if(!confirmNoSave())return false;document.location.href=\"index.php?module=Studio&action=wizard&wizard=ManageBackups&setFile={$_SESSION['studio']['selectedFileId']}\"'");
196
		return $buttons;
197
	}
198
	function getFormButtons(){
199
		$buttons = $this->generateButtons();
200
		return $this->buildImageButtons($buttons);
201
	}
202
	function getForm(){
203
		return $this->form  . <<<EOQ
204
		</form>
205
206
207
EOQ;
208
209
	}
210
211
212
213
	function getFiles($module, $fileId=false){
214
		if(empty($GLOBALS['studioDefs'][$module])){
215
			require_once('modules/'. $module . '/metadata/studio.php');
216
		}
217
		if($fileId){
218
			return 	$GLOBALS['studioDefs'][$module][$fileId];
219
		}
220
		return $GLOBALS['studioDefs'][$module];
221
	}
222
223
224
	function getWorkingFile($file, $refresh = false){
225
		$workingFile = 'working/' . $file;
226
		$customFile = create_custom_directory($workingFile);
227
		if($refresh || !file_exists($customFile)){
228
			copy($file, $customFile);
229
		}
230
		return $customFile;
231
	}
232
233
	function getSwapWith($value){
234
		return $value * 2 - 1;
235
	}
236
	/**
237
	 * takes the submited form and parses the file moving the fields around accordingly
238
	 * it also checks if the original file has a matching field and uses that field instead of attempting to generate a new one
239
	 */
240
	function handleSave() {
241
		$this->parseOldestFile($this->curFile);
242
		$fileDef = $this->getFiles($_SESSION['studio']['module'], $_SESSION['studio']['selectedFileId']);
243
		$type = $this->getFileType($fileDef['type']);
244
		$view = $this->curText;
245
		$counter = 0;
246
		$return_view = '';
247
		$slotCount = 0;
248
		$slotLookup = array();
249
		for ($i = 0; $i < sizeof($this->positions); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
250
			//used for reverse lookups to figure out where the associated slot is
251
			$slotLookup[$this->positions[$i][2]][$this->positions[$i][3]] = array('position'=>$i, 'value'=>$this->positions[$i][4]);
252
		}
253
254
		$customFields = $this->focus->custom_fields->getAllBeanFieldsView($type, 'html');
255
256
		//now we set it to the new values
257
258
		for ($i = 0; $i < sizeof($this->positions); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
259
			$slot = $this->positions[$i];
260
261
			if (empty($slot[3])) {
262
				$slotCount ++;
263
264
				//if the value in the request doesn't equal our current slot then something should be done
265
				if(isset($_REQUEST['slot_'.$slotCount]) && $_REQUEST['slot_'.$slotCount] != $slotCount){
266
267
					$swapValue = $_REQUEST['slot_'.$slotCount] ;
268
					//if its an int then its a simple swap
269
					if(is_numeric($swapValue)){
270
271
						$swapWith = $this->positions[$this->getSwapWith($swapValue)];
272
273
						//label
274
						$slotLookup[$slot[2]]['']['value'] = $this->positions[ $slotLookup[$swapWith[2]]['']['position']][4];
275
						//html
276
						$slotLookup[$slot[2]]['b']['value'] = $this->positions[ $slotLookup[$swapWith[2]]['b']['position']][4];
277
					}
278
					//now check if its a delete action
279
					if(strcmp('add:delete', $swapValue) == 0){
280
						//label
281
						$slotLookup[$slot[2]][$slot[3]]['value'] = '&nbsp;';
282
						//html
283
						$slotLookup[$slot[2]]['b']['value'] = '&nbsp;';
284
					}else{
285
286
						//now handle the adding of custom fields
287
						if(substr_count($swapValue, 'add:')){
288
							$addfield = explode('add:', $_REQUEST['slot_'.$slotCount], 2);
289
290
							//label
291
							$slotLookup[$slot[2]][$slot[3]]['value'] = $customFields[$addfield[1]]['label'] ;
292
							//html
293
							if(!empty($this->oldMatches[$addfield[1]])){
294
								//we have an exact match from the original file use that
295
								$slotLookup[$slot[2]]['b']['value'] = $this->oldMatches[$addfield[1]];
296
							}else{
297
								if(!empty($this->oldLabels[$customFields[$addfield[1]]['label']])){
298
									//we have matched the label from the original file use that
299
									$slotLookup[$slot[2]]['b']['value'] = $this->oldLabels[$customFields[$addfield[1]]['label']];
300
								}else{
301
									//no matches so use what we are generating
302
									$slotLookup[$slot[2]]['b']['value'] = $customFields[$addfield[1]]['html'];
303
								}
304
305
							}
306
307
						}
308
					}
309
				}
310
			}
311
		}
312
313
		for ($i = 0; $i < sizeof($this->positions); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
314
			$slot = $this->positions[$i];
315
			$explode = explode($slot[0], $view, 2);
316
			$explode[0] .= "<span sugar='". $slot[1] . $slot[2]. $slot[3]. "'>";
317
			$explode[1] = "</span sugar='" .$slot[1] ."'>".$explode[1];
318
319
			$return_view .= $explode[0].$slotLookup[$slot[2]][$slot[3]]['value'];
320
			$view = $explode[1];
321
			$counter ++;
322
		}
323
		$return_view .= $view;
324
325
		$this->saveFile('', $return_view);
326
		return $return_view;
327
	}
328
329
	function saveFile($file = '', $contents = false) {
330
		if (empty ($file)) {
331
			$file = $this->curFile;
332
		}
333
334
		$fp = sugar_fopen($file, 'w');
335
		$output = $contents ? $contents : $this->curText;
336
		if(strpos($file, 'SearchForm.html') > 0) {
337
			$fileparts = preg_split("'<!--\s*(BEGIN|END)\s*:\s*main\s*-->'", $output);
338
			if(!empty($fileparts) && count($fileparts) > 1) {
339
				//preg_replace_callback doesn't seem to work w/o anonymous method
340
				$output = preg_replace_callback("/name\s*=\s*[\"']([^\"']*)[\"']/Us",
341
				create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
342
	                                                     '$matches',
343
	                                                     '$name = str_replace(array("[", "]"), "", $matches[1]);
344
														  if((strpos($name, "LBL_") === 0) && (strpos($name, "_basic") === 0)) {
345
	                                                          return str_replace($name, $name . "_basic", $matches[0]);
346
	                                                      }
347
	                                                      return  $matches[0];'
348
	                                                      ),
349
	                                                      $fileparts[1]);
350
351
352
353
	                                                      $output = $fileparts[0] . '<!-- BEGIN:main -->' . $output . '<!-- END:main -->' . $fileparts[2];
354
			}
355
		}
356
357
		fwrite($fp, $output);
358
		fclose($fp);
359
	}
360
361
	function handleSaveLabels($module_name, $language){
362
		require_once('modules/Studio/LabelEditor/LabelEditor.php');
363
		LabelEditor::saveLabels($_REQUEST, $module_name, $language);
364
	}
365
366
	/**
367
	 * UTIL FUNCTIONS
368
	 */
369
	/**
370
	 * STATIC FUNCTION DISABLE INPUTS IN AN HTML STRING
371
	 *
372
	 */
373
	function disableInputs($str) {
374
		$match = array ("'(<input)([^>]*>)'si" => "\$1 disabled readonly $2",
375
    "'(<input)([^>]*?type[ ]*=[ ]*[\'\"]submit[\'\"])([^>]*>)'si" => "\$1 disabled readonly style=\"display:none\" $2",
376
     "'(<select)([^>]*)'si" => "\$1 disabled readonly $2",
377
		// "'<a .*>(.*)</a[^>]*>'siU"=>"\$1",
378
"'(href[\ ]*=[\ ]*)([\'])([^\']*)([\'])'si" => "href=\$2javascript:void(0);\$2 alt=\$2\$3\$2", "'(href[\ ]*=[\ ]*)([\"])([^\"]*)([\"])'si" => "href=\$2javascript:void(0)\$2 title=\$2\$3\$2");
379
		return preg_replace(array_keys($match), array_values($match), $str);
380
	}
381
382
	function enableLabelEditor($str) {
383
        global $mod_strings;
384
		$image = SugarThemeRegistry::current()->getImage( 'edit_inline', "onclick='studiojs.handleLabelClick(\"$2\", 1);' onmouseover='this.style.cursor=\"default\"'",null,null,'.gif',$mod_strings['LBL_EDIT']);
385
		$match = array ("'>[^<]*\{(MOD.)([^\}]*)\}'si" => "$image<span id='label$2' onclick='studiojs.handleLabelClick(\"$2\", 2);' >{".'$1$2' . "}</span><span id='span$2' style='display:none'><input type='text' id='$2' name='$2' msi='label' value='{".'$1$2' . "}' onblur='studiojs.endLabelEdit(\"$2\")'></span>");
386
		$keys = array_keys($match);
387
		$matches = array();
388
		preg_match_all($keys[0], $str, $matches, PREG_SET_ORDER);
389
		foreach($matches as $labelmatch){
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
390
			$label_name = 'label_' . $labelmatch[2];
391
			$this->form .= "\n<input type='hidden' name='$label_name'  id='$label_name' value='no_change'>";
392
393
		}
394
		return preg_replace(array_keys($match), array_values($match), $str);
395
	}
396
397
398
399
	function writeToCache($file, $view, $preview_file=false) {
400
		if (!is_writable($file)) {
401
			echo "<br><span style='color:red'>Warning: $file is not writeable. Please make sure it is writeable before continuing</span><br><br>";
402
		}
403
404
		if(!$preview_file){
405
			$file_cache = create_cache_directory('studio/'.$file);
406
		}else{
407
			$file_cache = create_cache_directory('studio/'.$preview_file);
408
		}
409
		$fp = sugar_fopen($file_cache, 'w');
410
		$view = $this->disableInputs($view);
411
		if(!$preview_file){
412
			$view = $this->enableLabelEditor($view);
413
		}
414
		fwrite($fp, $view);
415
		fclose($fp);
416
		return $this->cacheXTPL($file, $file_cache, $preview_file);
417
	}
418
419
	function populateRequestFromBuffer($file) {
420
		$results = array ();
421
		$temp = sugar_file_get_contents($file);
422
		preg_match_all("'name[\ ]*=[\ ]*[\']([^\']*)\''si", $buffer, $results);
423
		$res = $results[1];
424
		foreach ($res as $r) {
425
			$_REQUEST[$r] = $r;
426
		}
427
		preg_match_all("'name[\ ]*=[\ ]*[\"]([^\"]*)\"'si", $buffer, $results);
428
		$res = $results[1];
429
		foreach ($res as $r) {
430
			$_REQUEST[$r] = $r;
431
		}
432
433
		$_REQUEST['query'] = true;
434
		$_REQUEST['advanced'] = true;
435
436
	}
437
	function cacheXTPL($file, $cache_file, $preview_file = false) {
438
		global $beanList;
439
		//now if we have a backup_file lets use that instead of the original
440
		if($preview_file){
441
			$file  = $preview_file;
442
		}
443
444
445
		if(!isset($the_module))$the_module = $_SESSION['studio']['module'];
0 ignored issues
show
The variable $the_module seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
446
		$files = StudioParser::getFiles($the_module);
447
		$xtpl = $files[$_SESSION['studio']['selectedFileId']]['php_file'];
448
		$originalFile = $files[$_SESSION['studio']['selectedFileId']]['template_file'];
449
		$type = StudioParser::getFileType($files[$_SESSION['studio']['selectedFileId']]['type']);
450
		$buffer = sugar_file_get_contents($xtpl);
451
		$cache_file = create_cache_directory('studio/'.$file);
452
		$xtpl_cache = create_cache_directory('studio/'.$xtpl);
453
		$module = $this->workingModule;
454
455
		$form_string = "require_once('modules/".$module."/Forms.php');";
456
457
		if ($type == 'edit' || $type == 'detail') {
458
			if (empty ($_REQUEST['record'])) {
459
				$buffer = preg_replace('(\$xtpl[\ ]*=)', "\$focus->assign_display_fields('$module'); \$0", $buffer);
460
			} else {
461
				$buffer = preg_replace('(\$xtpl[\ ]*=)', "\$focus->retrieve('".$_REQUEST['record']."');\n\$focus->assign_display_fields('$module');\n \$0", $buffer);
462
			}
463
		}
464
		$_REQUEST['query'] = true;
465
		if (substr_count($file, 'SearchForm') > 0) {
466
			$temp_xtpl = new XTemplate($file);
467
			if ($temp_xtpl->exists('advanced')) {
468
469
				global $current_language, $beanFiles, $beanList;
470
				$mods = return_module_language($current_language, 'DynamicLayout');
471
				$class_name = $beanList[$module];
472
				require_once ($beanFiles[$class_name]);
473
				$mod = new $class_name ();
474
475
				$this->populateRequestFromBuffer($file);
476
				$mod->assign_display_fields($module);
477
				$buffer = str_replace(array ('echo $lv->display();','$search_form->parse("advanced");', '$search_form->out("advanced");', '$search_form->parse("main");', '$search_form->out("main");'), '', $buffer);
478
				$buffer = str_replace('echo get_form_footer();', '$search_form->parse("main");'."\n".'$search_form->out("main");'."\necho '<br><b>".translate('LBL_ADVANCED', 'DynamicLayout')."</b><br>';".'$search_form->parse("advanced");'."\n".'$search_form->out("advanced");'."\n \$sugar_config['list_max_entries_per_page'] = 1;", $buffer);
479
			}
480
		}else{
481
482
			if ($type == 'detail') {
483
				$buffer = str_replace('header(', 'if(false) header(', $buffer);
484
			}
485
		}
486
487
		$buffer = str_replace($originalFile, $cache_file, $buffer);
488
		$buffer = "<?php\n\$sugar_config['list_max_entries_per_page'] = 1;\n ?>".$buffer;
489
490
		$buffer = str_replace($form_string, '', $buffer);
491
		$buffer = $this->disableInputs($buffer);
492
		$xtpl_fp_cache = sugar_fopen($xtpl_cache, 'w');
493
		fwrite($xtpl_fp_cache, $buffer);
494
		fclose($xtpl_fp_cache);
495
		return $xtpl_cache;
496
	}
497
498
	/**
499
	 * Yahoo Drag & Drop Support
500
	 */
501
	////<script type="text/javascript" src="modules/Studio/studio.js" ></script>
502
	function yahooJS() {
503
		$custom_module = $_SESSION['studio']['module'];
504
		$custom_type = $this->curType;
505
		$v = getVersionedPath('');
506
		return<<<EOQ
507
		<style type='text/css'>
508
		.slot {
509
		border-width:1px;border-color:#999999;border-style:solid;padding:0px 1px 0px 1px;margin:2px;cursor:move;
510
511
	}
512
513
	.slotB {
514
	border-width:0;cursor:move;
515
516
	}
517
	</style>
518
519
	<!-- Namespace source file -->
520
521
	<script type="text/javascript" src="modules/Studio/JSTransaction.js?v=$v" ></script>
522
	<script>
523
	var jstransaction = new JSTransaction();
524
	</script>
525
526
	<!-- Drag and Drop source file -->
527
	<script type="text/javascript" src="include/javascript/yui/build/dragdrop/dragdrop.js?v=$v" ></script>
528
	<script type="text/javascript" src="modules/Studio/studiodd.js?v=$v" ></script>
529
	<script type="text/javascript" src="modules/Studio/studio.js?v=$v" ></script>
530
	<script>
531
532
	var yahooSlots = [];
533
534
	function dragDropInit(){
535
536
	YAHOO.util.DDM.mode = YAHOO.util.DDM.POINT;
537
538
	for(mj = 0; mj <= $this->yahooSlotCount; mj++){
539
	yahooSlots["slot" + mj] = new ygDDSlot("slot" + mj, "studio");
540
	}
541
	for(mj = 0; mj < dyn_field_count; mj++){
542
	yahooSlots["dyn_field_" + mj] = new ygDDSlot("dyn_field_" + mj, "studio");
543
	}
544
	// initPointMode();
545
	yahooSlots['s_field_delete'] =  new YAHOO.util.DDTarget("s_field_delete", 'studio');
546
	}
547
548
	YAHOO.util.Event.addListener(window, "load", dragDropInit);
549
	var custom_module = '$custom_module';
550
	var custom_view = '$custom_type';
551
552
			</script>
553
554
EOQ;
555
556
	}
557
558
	/**
559
	 * delete:-1
560
	 * add:2000
561
	 * swap: 0 - 1999
562
	 *
563
	 */
564
	function addSlotToForm($slot_count, $display_count){
565
		$this->form .= "\n<input type='hidden' name='slot_$slot_count'  id='slot_$display_count' value='$slot_count'>";
566
	}
567
	function prepSlots() {
568
		$view = $this->curText;
569
		$counter = 0;
570
		$return_view = '';
571
		$slotCount = 0;
572
		for ($i = 0; $i < sizeof($this->positions); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
573
			$slot = $this->positions[$i];
574
			$class = '';
575
576
			if (empty($this->positions[$i][3])) {
577
578
				$slotCount ++;
579
				$class = " class='slot' ";
580
				$displayCount = $slotCount. $this->positions[$i][3];
581
				$this->addSlotToForm($slotCount, $displayCount);
582
			}else{
583
				$displayCount = $slotCount. $this->positions[$i][3];
584
			}
585
586
587
			$explode = explode($slot[0], $view, 2);
588
			$style = '';
589
			$explode[0] .= "<div id = 'slot$displayCount'  $class style='cursor: move$style'>";
590
			$explode[1] = "</div>".$explode[1];
591
			$return_view .= $explode[0].$slot[4];
592
			$view = $explode[1];
593
			$counter ++;
594
		}
595
		$this->yahooSlotCount = $slotCount;
596
		$newView = $return_view.$view;
597
		$newView = str_replace(array ('<slot>', '</slot>'), array ('', ''), $newView);
598
599
		return $newView;
600
	}
601
602
	function parseOldestFile($file){
603
	 ob_clean();
604
		require_once('modules/Studio/SugarBackup.php');
605
		$file = str_replace('custom/working/', '' ,$file);
606
607
		$filebk = SugarBackup::oldestBackup($file);
608
		$oldMatches = array();
609
		$oldLabels = array();
610
		// echo $filebk;
611
		if($filebk){
612
			$content = file_get_contents($filebk);
613
			$positions = $this->parsePositions($content, true);
614
			for ($i = 0; $i < sizeof($positions); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
615
				$position = $positions[$i];
616
				//used for reverse lookups to figure out where the associated slot is
617
				$slotLookup[$position[2]][$position[3]] = array('position'=>$i, 'value'=>$position[4]);
618
				$names = $this->parseNames($position[4]);
619
				$labels = $this->parseLabels($position[4]);
620
621
				foreach($names as $name){
0 ignored issues
show
The expression $names of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
622
					$oldMatches[$name[1]] = $position[0];
623
				}
624
				foreach($labels as $label){
625
					$oldLabels[$label[0]] = $position[2];
626
				}
627
			}
628
629
630
631
		}
632
		foreach($oldLabels as $key=>$value){
633
			$oldLabels[$key] = $slotLookup[$value]['b']['value'];
634
		}
635
636
		$this->oldLabels = $oldLabels;
637
		$this->oldMatches = $oldMatches;
638
639
	}
640
641
642
	function clearWorkingDirectory(){
643
644
		$file = 'custom/working/';
645
		if(file_exists($file)){
646
647
			rmdir_recursive($file);
648
		}
649
650
		return true;
651
652
	}
653
654
	/**
655
	 * UPGRADE TO SMARTY
656
	 */
657
	function upgradeToSmarty() {
658
		return str_replace('{', '{$', $this->curText);
659
	}
660
}
661
?>
662