Completed
Pull Request — master (#5258)
by Sean
29:40 queued 19:27
created

TinyMCE_Compressor::addFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
/**
3
 * tinymce.gzip.php
4
 *
5
 * Copyright, Moxiecode Systems AB
6
 * Released under LGPL License.
7
 *
8
 * License: http://tinymce.moxiecode.com/license
9
 * Contributing: http://tinymce.moxiecode.com/contributing
10
 */
11
12
// CUSTOM SilverStripe: Copied from Core.php
13
if(!function_exists('getSysTempDir')) {
14
	function getSysTempDir() {
15
		if(function_exists('sys_get_temp_dir')) {
16
			$sysTmp = sys_get_temp_dir();
17
		} elseif(isset($_ENV['TMP'])) {
18
			$sysTmp = $_ENV['TMP'];    	
19
		} else {
20
			$tmpFile = tempnam('adfadsfdas','');
21
			unlink($tmpFile);
22
			$sysTmp = dirname($tmpFile);
23
		}
24
		return $sysTmp;
25
	}
26
}
27
// CUSTOM END
28
29
// Handle incoming request if it's a script call
30
if (TinyMCE_Compressor::getParam("js")) {
31
	// Default settings
32
	$tinyMCECompressor = new TinyMCE_Compressor(array(
33
	/*
34
	 * Add any site-specific defaults here that you may wish to implement. For example:
35
	 *
36
	 *  "languages" => "en",
37
	 *  "cache_dir" => realpath(dirname(__FILE__) . "/../../_cache"),
38
	 *  "files"     => "somescript,anotherscript",
39
	 *  "expires"   => "1m",
40
	 */
41
		// CUSTOM SilverStripe
42
		'cache_dir' => getSysTempDir()
43
		// CUSTOM END
44
	));
45
46
	// Handle request, compress and stream to client
47
	$tinyMCECompressor->handleRequest();
48
}
49
50
/**
51
 * This class combines and compresses the TinyMCE core, plugins, themes and
52
 * language packs into one disk cached gzipped request. It improves the loading speed of TinyMCE dramatically but
53
 * still provides dynamic initialization.
54
 *
55
 * Example of direct usage:
56
 * require_once("../js/tinymce/tinymce.gzip.php");
57
 *
58
 * // Renders script tag with compressed scripts
59
 * TinyMCE_Compressor::renderTag(array(
60
 *    "url" => "../js/tinymce/tinymce.gzip.php",
61
 *    "plugins" => "pagebreak,style",
62
 *    "themes" => "advanced",
63
 *    "languages" => "en"
64
 * ));
65
 */
66
class TinyMCE_Compressor {
67
	private $files, $settings;
68
	private static $defaultSettings = array(
69
		"plugins"    => "",
70
		"themes"     => "",
71
		"languages"  => "",
72
		"disk_cache" => false,
73
		"expires"    => "30d",
74
		"cache_dir"  => "",
75
		"compress"   => true,
76
		"files"      => "",
77
		"source"     => true,
78
	);
79
80
	/**
81
	 * Constructs a new compressor instance.
82
	 *
83
	 * @param Array $settings Name/value array with non-default settings for the compressor instance.
84
	 */
85
	public function __construct($settings = array()) {
86
		$this->settings = array_merge(self::$defaultSettings, $settings);
87
88
		if (empty($this->settings["cache_dir"])) {
89
			$this->settings["cache_dir"] = dirname(__FILE__);
90
		}
91
	}
92
93
	/**
94
	 * Adds a file to the concatenation/compression process.
95
	 *
96
	 * @param String $path Path to the file to include in the compressed package/output.
0 ignored issues
show
Bug introduced by
There is no parameter named $path. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
97
	 */
98
	public function &addFile($file) {
99
		$this->files .= ($this->files ? "," : "") . $file;
100
101
		return $this;
102
	}
103
104
	/**
105
	 * Handles the incoming HTTP request and sends back a compressed script depending on settings and client support.
106
	 */
107
	public function handleRequest() {
108
		$files = array();
109
		$supportsGzip = false;
110
		$expiresOffset = $this->parseTime($this->settings["expires"]);
111
		$tinymceDir = dirname(__FILE__);
112
113
		// Plugins
114
		$plugins = self::getParam("plugins");
115
		if ($plugins) {
116
			$this->settings["plugins"] = $plugins;
117
		}
118
119
		$plugins = array_unique(preg_split('/,/', $this->settings["plugins"], -1, PREG_SPLIT_NO_EMPTY));
120
121
		// Themes
122
		$themes = self::getParam("themes");
123
		if ($themes) {
124
			$this->settings["themes"] = $themes;
125
		}
126
127
		$themes = array_unique(preg_split('/,/', $this->settings["themes"], -1, PREG_SPLIT_NO_EMPTY));
128
129
		// Languages
130
		$languages = self::getParam("languages");
131
		if ($languages) {
132
			$this->settings["languages"] = $languages;
133
		}
134
135
		$languages = array_unique(preg_split('/,/', $this->settings["languages"], -1, PREG_SPLIT_NO_EMPTY));
136
137
		// Files
138
		$tagFiles = self::getParam("files");
139
		if ($tagFiles) {
140
			$this->settings["files"] = $tagFiles;
141
		}
142
143
		// Diskcache option
144
		$diskCache = self::getParam("diskcache");
145
		if ($diskCache) {
146
			$this->settings["disk_cache"] = ($diskCache === "true");
147
		}
148
149
		// Source or minified version
150
		$src = self::getParam("src");
151
		if ($src) {
152
			$this->settings["source"] = ($src === "true");
153
		}
154
155
		// Add core js
156
		if (self::getParam("core", "true") === "true") {
157
			$files[] = "tinymce";
158
		}
159
160
		// Add core languages
161
		foreach ($languages as $language) {
162
			$files[] = "langs/" . $language;
163
		}
164
165
		// Add plugins
166
		foreach ($plugins as $plugin) {
167
			$files[] = "plugins/" . $plugin . "/plugin";
168
169
			foreach ($languages as $language) {
170
				$files[] = "plugins/" . $plugin . "/langs/" . $language;
171
			}
172
		}
173
174
		// Add themes
175
		foreach ($themes as $theme) {
176
			$files[] = "themes/" . $theme . "/theme";
177
178
			foreach ($languages as $language) {
179
				$files[] = "themes/" . $theme . "/langs/" . $language;
180
			}
181
		}
182
183
		// Add any specified files.
184
		$allFiles = array_merge($files, array_unique(preg_split('/,/', $this->settings['files'], -1, PREG_SPLIT_NO_EMPTY)));
185
186
		// Process source files
187
		for ($i = 0; $i < count($allFiles); $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...
188
			$file = $allFiles[$i];
189
190
			if ($this->settings["source"] && file_exists($file . ".js")) {
191
				$file .= ".js";
192
			} else if (file_exists($file . ".min.js"))  {
193
				$file .= ".min.js";
194
			} else {
195
				$file = "";
196
			}
197
198
			$allFiles[$i] = $file;
199
		}
200
201
		// Generate hash for all files
202
		$hash = md5(implode('', $allFiles) . $_SERVER['SCRIPT_NAME']);
203
204
		// Check if it supports gzip
205
		$zlibOn = ini_get('zlib.output_compression') || (ini_set('zlib.output_compression', 0) === false);
206
		$encodings = (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) ? strtolower($_SERVER['HTTP_ACCEPT_ENCODING']) : "";
207
		$encoding = preg_match( '/\b(x-gzip|gzip)\b/', $encodings, $match) ? $match[1] : "";
208
209
		// Is northon antivirus header
210
		if (isset($_SERVER['---------------'])) {
211
			$encoding = "x-gzip";
212
		}
213
214
		$supportsGzip = $this->settings['compress'] && !empty($encoding) && !$zlibOn && function_exists('gzencode');
215
216
		// Set cache file name
217
		$cacheFile = $this->settings["cache_dir"] . "/tinymce.gzip-" . $hash . ($supportsGzip ? ".gz" : ".js");
218
219
 		// Set headers
220
		header("Content-type: text/javascript");
221
		header("Vary: Accept-Encoding");  // Handle proxies
222
		header("Expires: " . gmdate("D, d M Y H:i:s", time() + $expiresOffset) . " GMT");
223
		header("Cache-Control: public, max-age=" . $expiresOffset);
224
225
		if ($supportsGzip) {
226
			header("Content-Encoding: " . $encoding);
227
		}
228
229
		// Use cached file
230
		if ($this->settings['disk_cache'] && file_exists($cacheFile)) {
231
			readfile($cacheFile);
232
			return;
233
		}
234
235
		// Set base URL for where tinymce is loaded from
236
		$buffer = "var tinyMCEPreInit={base:'" . dirname($_SERVER["SCRIPT_NAME"]) . "',suffix:'.min'};";
237
238
		// Load all tinymce script files into buffer
239
		foreach ($allFiles as $file) {
240
			if ($file) {
241
				$fileContents = $this->getFileContents($tinymceDir . "/" . $file);
242
//				$buffer .= "\n//-FILE-$tinymceDir/$file (". strlen($fileContents) . " bytes)\n";
243
				$buffer .= $fileContents;
244
			}
245
		}
246
247
		// Mark all themes, plugins and languages as done
248
		$buffer .= 'tinymce.each("' . implode(',', $files) . '".split(","),function(f){tinymce.ScriptLoader.markDone(tinyMCE.baseURL+"/"+f+".js");});';
249
250
		// Compress data
251
		if ($supportsGzip) {
252
			$buffer = gzencode($buffer, 9, FORCE_GZIP);
253
		}
254
255
		// Write cached file
256
		if ($this->settings["disk_cache"]) {
257
			@file_put_contents($cacheFile, $buffer);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
258
		}
259
260
		// Stream contents to client
261
		echo $buffer;
262
	}
263
264
	/**
265
	 * Renders a script tag that loads the TinyMCE script.
266
	 *
267
	 * @param Array $settings Name/value array with settings for the script tag.
0 ignored issues
show
Documentation introduced by
There is no parameter named $settings. Did you maybe mean $tagSettings?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
268
	 * @param Bool  $return   The script tag is return instead of being output if true
269
	 * @return String the tag is returned if $return is true  
270
	 */
271
	public static function renderTag($tagSettings, $return = false) {
272
		$settings = array_merge(self::$defaultSettings, $tagSettings);
273
274
		if (empty($settings["cache_dir"])) {
275
			$settings["cache_dir"] = dirname(__FILE__);
276
		}
277
278
		$scriptSrc = $settings["url"] . "?js=1";
279
280
		// Add plugins
281
		if (isset($settings["plugins"])) {
282
			$scriptSrc .= "&plugins=" . (is_array($settings["plugins"]) ? implode(',', $settings["plugins"]) : $settings["plugins"]);
283
		}
284
285
		// Add themes
286
		if (isset($settings["themes"])) {
287
			$scriptSrc .= "&themes=" . (is_array($settings["themes"]) ? implode(',', $settings["themes"]) : $settings["themes"]);
288
		}
289
290
		// Add languages
291
		if (isset($settings["languages"])) {
292
			$scriptSrc .= "&languages=" . (is_array($settings["languages"]) ? implode(',', $settings["languages"]) : $settings["languages"]);
293
		}
294
295
		// Add disk_cache
296
		if (isset($settings["disk_cache"])) {
297
			$scriptSrc .= "&diskcache=" . ($settings["disk_cache"] === true ? "true" : "false");
298
		}
299
300
		// Add any explicitly specified files if the default settings have been overriden by the tag ones
301
		/*
302
		 * Specifying tag files will override (rather than merge with) any site-specific ones set in the 
303
		 * TinyMCE_Compressor object creation.  Note that since the parameter parser limits content to alphanumeric
304
		 * only base filenames can be specified.  The file extension is assumed to be ".js" and the directory is
305
		 * the TinyMCE root directory.  A typical use of this is to include a script which initiates the TinyMCE object. 
306
		 */
307
		if (isset($tagSettings["files"])) {
308
			$scriptSrc .= "&files=" .(is_array($settings["files"]) ? implode(',', $settings["files"]) : $settings["files"]);
309
		}
310
311
		// Add src flag
312
		if (isset($settings["source"])) {
313
			$scriptSrc .= "&src=" . ($settings["source"] === true ? "true" : "false");
314
		}
315
316
		$scriptTag = '<script src="' . htmlspecialchars($scriptSrc) . '"></script>';
317
318
		if ($return) {
319
			return $scriptTag;
320
		} else {
321
			echo $scriptTag;
322
		}
323
	}
324
325
	/**
326
	 * Returns a sanitized query string parameter.
327
	 *
328
	 * @param String $name Name of the query string param to get.
329
	 * @param String $default Default value if the query string item shouldn't exist.
330
	 * @return String Sanitized query string parameter value.
331
	 */
332
	public static function getParam($name, $default = "") {
333
		if (!isset($_GET[$name])) {
334
			return $default;
335
		}
336
337
		return preg_replace("/[^0-9a-z\-_,]+/i", "", $_GET[$name]); // Sanatize for security, remove anything but 0-9,a-z,-_,
338
	}
339
340
	/**
341
	 * Parses the specified time format into seconds. Supports formats like 10h, 10d, 10m.
342
	 *
343
	 * @param String $time Time format to convert into seconds.
344
	 * @return Int Number of seconds for the specified format.
345
	 */
346
	private function parseTime($time) {
347
		$multipel = 1;
348
349
		// Hours
350
		if (strpos($time, "h") > 0) {
351
			$multipel = 3600;
352
		}
353
354
		// Days
355
		if (strpos($time, "d") > 0) {
356
			$multipel = 86400;
357
		}
358
359
		// Months
360
		if (strpos($time, "m") > 0) {
361
			$multipel = 2592000;
362
		}
363
364
		// Trim string
365
		return intval($time) * $multipel;
366
	}
367
368
	/**
369
	 * Returns the contents of the script file if it exists and removes the UTF-8 BOM header if it exists.
370
	 *
371
	 * @param String $file File to load.
372
	 * @return String File contents or empty string if it doesn't exist.
373
	 */
374
	private function getFileContents($file) {
375
		$content = file_get_contents($file);
376
377
		// Remove UTF-8 BOM
378
		if (substr($content, 0, 3) === pack("CCC", 0xef, 0xbb, 0xbf)) {
379
			$content = substr($content, 3);
380
		}
381
382
		return $content;
383
	}
384
}
385
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
386