PluginProbe
Microthemer Lite – Visual Editor to Customize CSS / trunk
Microthemer Lite – Visual Editor to Customize CSS vtrunk
trunk 5.0.0.2
microthemer / src / FileTrait.php

FileTrait.php in Microthemer Lite – Visual Editor to Customize CSS trunk, at src/FileTrait.php

753 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Microthemer;
4
5 use Microthemer\Dependencies\Minify;
6
7 /*
8 * Direct PHP filesystem APIs are used for micro-themes under wp-content (typically directly writable).
9 * WP_Filesystem can prompt for FTP credentials and break the AJAX editor; unzip still uses WP_Filesystem.
10 *
11 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen
12 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
13 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fclose
14 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fread
15 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_mkdir
16 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
17 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_chmod
18 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_touch
19 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
20 * phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_is_writeable
21 * phpcs:disable WordPress.WP.AlternativeFunctions.unlink_unlink
22 * phpcs:disable WordPress.WP.AlternativeFunctions.rename_rename
23 */
24 trait FileTrait {
25
26 function setup_micro_themes_dir($activated = false){
27
28 // create micro-themes directory and some subdirectories
29 // the parent directory is created so just need define subdirectories
30 $directories = array(
31 $this->micro_root_dir . 'mt/conditional/draft/',
32 $this->micro_root_dir . 'mt/conditional/active/',
33 $this->micro_root_dir . 'mt/js/draft/npm/',
34 $this->micro_root_dir . 'mt/js/active/npm/',
35 $this->micro_root_dir . 'mt/cache/themes/' . get_stylesheet() . '/',
36 $this->micro_root_dir . 'mt/cache/tailwind/classes/',
37 $this->micro_root_dir . 'mt/cache/tailwind/styles/',
38 $this->micro_root_dir . 'mt/cache/content/',
39 $this->micro_root_dir . 'Content/',
40 );
41
42 foreach ($directories as $path){
43
44 if ( !is_dir($path) ) {
45
46 // create micro-themes dirs or log an error
47 if ( !wp_mkdir_p($path) ) {
48
49 $this->log(
50 esc_html__('/micro-themes create directory error', 'microthemer'),
51 '<p>' . sprintf(
52 /* translators: %s: directory path */
53 esc_html__('WordPress was not able to create the directory: %s', 'microthemer'),
54 $this->root_rel($path)
55 ) . $this->permissionshelp . '</p>'
56 );
57
58 return false;
59 }
60 }
61 }
62
63 /*else {
64 //micro-themes dir does exist, clean lose pack files that may exist due to past bug
65 $this->maybe_clean_micro_root(); // 7.7.2016 - we can remove after a few months.
66 }*/
67
68 // create _scss dir if it doesn't exist (at some point)
69
70 // copy animation-events over if needed
71 if (!$this->maybe_copy_to_micro_root($activated)) {
72 return false;
73 };
74
75 // update the template cache for the current theme if not created yet
76 Common::maybeUpdateTemplateCache($this->micro_root_dir);
77
78 // also create blank active-styles else 404 before user adds styles
79 /*$prime_files = array(
80 $this->micro_root_dir.'active-styles.css',
81 $this->micro_root_dir.'min.active-styles.css',
82 $this->micro_root_dir.'draft-styles.css',
83 $this->micro_root_dir.'min.draft-styles.css',
84 $this->micro_root_dir.'active-styles.scss',
85 );
86
87 if (!$this->maybe_create_stylesheet($prime_files)) {
88 return false;
89 }*/
90
91 // all good
92 return true;
93 }
94
95 function dir_loop($dir, $result = array()) {
96
97 foreach(scandir($dir) as $filename) {
98
99 if ($filename[0] === '.' || $filename[0] === '..' || $filename === 'stock' || $filename === 'mt' || $filename === 'Content') {
100 continue;
101 }
102
103 //wp_die('$filename: <pre>'.print_r($filename, 1).'</pre>' );
104
105 $filePath = $dir . DIRECTORY_SEPARATOR . $filename;
106
107 if (is_dir($filePath)) {
108 $result[$filename] = $this->dir_loop($filePath);
109 }
110
111 else {
112 if ($this->is_screenshot($filename)){
113 $result['screenshot'] = $filename;
114 } else {
115 $result[$filename] = 1;
116 }
117 }
118 }
119
120 // sort alphabetically
121 if (is_array($result)) {
122 ksort($result);
123 }
124
125 return $result;
126 }
127
128 // get extension
129 function has_extension($file) {
130 return count(explode('.', $file)) > 1;
131 //return preg_match('/\..+$/', explode('?', $file)[0]);
132 }
133
134 // create active-styles if it doesn't already exist
135 function maybe_create_stylesheet($prime_files){
136 if (is_array($prime_files)){
137 foreach($prime_files as $key => $file){
138 if (!file_exists($file)) {
139 if (!$write_file = @fopen($file, 'w')) {
140 $this->log(
141 esc_html__('Create stylesheet error', 'microthemer'),
142 '<p>' . esc_html__('WordPress does not have permission to create: ', 'microthemer') .
143 $this->root_rel($file) . '. '.$this->permissionshelp.'</p>'
144 );
145 return false;
146 }
147 fclose($write_file);
148 }
149 }
150 }
151 return true;
152 }
153
154 // copy files from Microthemer plugin dir to micro-themes for use when MT is inactive
155 function maybe_copy_to_micro_root($activated){
156
157 $orig_files = array(
158 '/js-min/animation-events.js',
159 '/stock/stock.zip',
160 '/src/autoload.php',
161 '/src/AssetLoad.php',
162 '/src/Logic.php',
163 '/src/Helper.php',
164
165 '/src/FrontAndBackTrait.php',
166 '/src/TimerTrait.php',
167 '/src/Content/AssetLoadContent.php',
168 '/src/Content/HTML.php',
169 '/src/Content/ContentHelper.php',
170
171 '/includes/animation/animation-code.inc.php',
172 '/js-min/amender.zip',
173 '/js-min/npm.zip',
174 );
175
176 $new_files = array(
177 'animation-events.js',
178 'stock.zip',
179 'autoload.php',
180 'AssetLoad.php',
181 'Logic.php',
182 'Helper.php',
183 'FrontAndBackTrait.php',
184 'TimerTrait.php',
185 'Content/AssetLoadContent.php',
186 'Content/HTML.php',
187 'Content/ContentHelper.php',
188 'animation-code.inc.php',
189 'mt/js/amender.zip',
190 'mt/js/draft/npm.zip',
191 );
192
193 // unzipped equivalents of the zip archives - preferred when present
194 // (zip files are not permitted in the wp.org build, which ships these assets loose)
195 $loose_dirs = array(
196 'stock.zip' => 'images/stock-min/',
197 'mt/js/amender.zip' => 'js-min/front-deps/amender/',
198 'mt/js/draft/npm.zip' => 'js-min/front-deps/draft/npm/',
199 );
200 $i = 0;
201
202 foreach($orig_files as $file){
203
204 $orig = $this->thisplugindir . $file;
205 $newFileName = $new_files[$i];
206 $dirName = trailingslashit(explode('.', $newFileName)[0]);
207 $new = $this->micro_root_dir . $newFileName;
208 $i++;
209 $isZipFile = strpos($newFileName, '.zip');
210
211 // copy the unzipped directory if the build ships one instead of a zip
212 if ($isZipFile !== false && isset($loose_dirs[$newFileName])){
213
214 $looseSource = $this->thisplugindir . $loose_dirs[$newFileName];
215
216 if (is_dir($looseSource)){
217
218 if ($activated || !is_dir($this->micro_root_dir . $dirName)){
219
220 $subdir = $newFileName === 'stock.zip' ? '' : $dirName;
221 $allowOverwrite = $newFileName !== 'mt/js/draft/npm.zip';
222
223 if ($this->copyFolder(
224 trailingslashit($looseSource),
225 trailingslashit($this->micro_root_dir . $subdir),
226 false, false, $allowOverwrite
227 ) === false){
228 $this->log(
229 esc_html__('Folder not copied', 'microthemer'),
230 '<p>' . sprintf(
231 /* translators: %s: plugin subdirectory name */
232 esc_html__('Plugin folder (%s) could not be copied to the /micro-themes directory.', 'microthemer'),
233 $loose_dirs[$newFileName]
234 ) . '</p>',
235 'error'
236 );
237 }
238 }
239
240 continue;
241 }
242 }
243
244 // amender.zip will not be available if content is not included
245 if (!file_exists($orig)){
246 continue;
247 }
248
249 // if we are updating / activating
250 if ($activated
251
252 // or it's a zip file and the folder hasn't already been extracted
253 || ($isZipFile&& !is_dir($this->micro_root_dir. $dirName))
254
255 // or it's any other file, which doesn't exist
256 || (!$isZipFile && !file_exists($new))
257 ){
258
259 //wp_die('We need to copy file: '. $this->micro_root_dir.'stock');
260
261 // attempt copy, and notify if it fails
262 if (!copy($orig, $new)){
263 $this->log(
264 esc_html__('File not copied', 'microthemer'),
265 '<p>' . sprintf(
266 /* translators: %s: plugin file name */
267 esc_html__('Plugin file (%s) could not be copied to the
268 /micro-themes directory.', 'microthemer'),
269 $file
270 ) . '</p>',
271 'error'
272 );
273 return false;
274 }
275
276 // copy was successful
277 else {
278
279 if ($isZipFile){
280
281 $error_logged = false;
282 $success = false;
283
284 //wp_die('Copy successful, try extracting '. $newFileName);
285
286 // try WordPress function fallback
287 if (!$success){
288
289 $subdir = $newFileName === 'stock.zip' ? '' : $dirName;
290
291 $allowOverwrite = $newFileName !== 'mt/js/draft/npm.zip';
292 $result = $this->wp_extract_files($this->micro_root_dir . $subdir, $new, $allowOverwrite);
293 $success = $result['success'];
294
295 if (!$success){
296
297 //wp_die('Extract WP attempt'. print_r($result, true));
298
299 $this->log(
300 esc_html__('Extract zip error', 'microthemer'),
301 '<p>' . $newFileName . ' could not be extracted to ' . $this->root_rel($this->micro_root_dir.$dirName) .'</p>' . '<p><pre>'. print_r($result['data']->errors, true).'</pre></p>'
302 );
303
304 $error_logged = true;
305 }
306
307 }
308
309 // try PCLZip library fallback - no this returns true even though nothing happens
310 // maye need to update the condition, but WP does this anyway.
311 /*if (!$success){
312 $success = $this->extract_files($this->micro_root_dir, $new);
313 $error_logged = true;
314 }*/
315
316 if ($success){
317 //wp_die('Extract successful: '. $this->micro_root_dir.'stock');
318 @unlink($new);
319 }
320
321 else {
322 if (!$error_logged){
323 $this->log(
324 esc_html__('Extract zip error', 'microthemer'),
325 '<p>' . $newFileName . ' could not be extracted to ' . $this->root_rel($this->micro_root_dir.$dirName.'/') .'</p>'
326 );
327 }
328
329 }
330
331
332 }
333 }
334 }
335 }
336
337 return true;
338 }
339
340 // Copy an entire folder
341 function copyFolder($from, $to, $minifyCSS = false, $minifyJS = false, $overwrite = true, $recursive = true) {
342
343 if (!is_dir($from)) {
344 return false; // bail if no directory to copy
345 } if (!is_dir($to)) {
346 if (!wp_mkdir_p($to)) {
347 return false; // bail if we can't create destination folder
348 };
349 }
350
351 // Copy folder files recursively
352 $dir = opendir($from);
353 while (($ff = readdir($dir)) !== false) {
354 if ($ff != "." && $ff != "..") {
355 if (is_dir("$from$ff")) {
356 if ($recursive){
357 $this->copyFolder("$from$ff/", "$to$ff/", $minifyCSS, $minifyJS, $overwrite, $recursive);
358 }
359 } else {
360 if ($overwrite || !file_exists("$to$ff")){
361
362 // copy or copy and minify the file
363 $ext = $this->get_extension($ff);
364 $doMinify = $ext === 'css' && $minifyCSS || $ext === 'js' && $minifyJS;
365 if ($doMinify){
366 $this->minify("$from$ff", $ext, "$to$ff");
367 } else {
368 copy("$from$ff", "$to$ff");
369 }
370 }
371
372 }
373 }
374 }
375
376 closedir($dir);
377 }
378
379 // clean lose pack files that may exist due to past bug
380 function maybe_clean_micro_root(){
381 $files = array(
382 'meta.txt',
383 'debug-save.txt',
384 'debug-current.txt',
385 'debug-pulled-data.txt',
386 'debug-selective-export.txt',
387 'debug-merge.txt',
388 'debug-overwrite.txt'
389 );
390 foreach ($files as $key => $file){
391 $file = $this->micro_root_dir . $file;
392 if (file_exists($file)){
393 unlink($file);
394 }
395 }
396 }
397
398
399 function wp_extract_files($dest, $zip_file, $allowOverwrite = true){
400
401 global $wp_filesystem;
402
403 // ensure file_system object is initiated
404 if (!$wp_filesystem){
405
406 // call file system manually if function not loaded yet
407 if (!function_exists('WP_Filesystem')){
408
409 $fileSystemClass = ABSPATH . 'wp-admin/includes/file.php';
410
411 if (!file_exists($fileSystemClass)){
412 return false;
413 }
414
415 // include class manually
416 require_once($fileSystemClass);
417 }
418
419 // initiate file_system, which unzip_file assumes is set up
420 \WP_Filesystem();
421
422 }
423
424 // Support unzip without overwriting existing - needed for vendor JS scripts which should not auto-update
425 $unzipDir = $dest;
426 if (!$allowOverwrite){
427 $unzipDir = untrailingslashit($dest) . '/tmp/';
428 }
429
430 // Unzip
431 $result = unzip_file($zip_file, $unzipDir);
432
433 // Copy unzipped from tmp dir if being careful not to overwrite
434 if ($result === TRUE){
435 if (!$allowOverwrite){
436
437 // copy files without overwriting
438 $this->copyFolder($unzipDir, $dest, false, false, false);
439
440 // Delete tmp directory
441 $this->destroyDirectoryAndContents($unzipDir);
442 }
443 }
444
445 /*if (str_contains($zip_file, 'amender.zip')){
446 wp_die('Result: '. print_r([$allowOverwrite, $zip_file, $unzipDir, $result], true));
447 }*/
448
449 return array(
450 'success' => ($result === TRUE),
451 'data' => $result
452 );
453 }
454
455 function destroyDirectoryAndContents($dir, $baseDir = null) {
456
457 if (!is_dir($dir)) {
458 return false;
459 }
460
461 // If this is the initial call, set the trusted base directory.
462 if ($baseDir === null) {
463 $baseDir = realpath($dir);
464 if ($baseDir === false) {
465 $this->log('Directory Deletion Error', '<p>Could not resolve the base path for deletion.</p>');
466 return false;
467 }
468 }
469
470 $items = scandir($dir);
471 foreach ($items as $item) {
472
473 if ($item == '.' || $item == '..') {
474 continue;
475 }
476
477 $path = $dir . DIRECTORY_SEPARATOR . $item;
478
479 if (!$this->checkExpectedPath($path, $baseDir)) {
480 $this->log('Directory Deletion Error', '<p>Wrong directory</p>');
481 return false;
482 }
483
484 if (is_dir($path)) {
485 if (!$this->destroyDirectoryAndContents($path, $baseDir)) {
486 return false;
487 }
488 } else {
489 if (!unlink($path)) {
490 return false;
491 }
492 }
493 }
494
495 return rmdir($dir);
496 }
497
498 function extract_files_native($dest, $zip_file){
499
500 $res = false;
501
502 if (class_exists( 'ZipArchive')){
503 $zip = new \ZipArchive;
504 $res = $zip->open($zip_file);
505 }
506
507 if ($res === TRUE) {
508 $zip->extractTo($dest);
509 $zip->close();
510 return true;
511 } else {
512 return false;
513 }
514 }
515
516 // handle zip extraction
517 function extract_files($dir, $file) {
518
519 // tap into native WP zip handling
520 if( !class_exists('PclZip')) {
521 require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
522 }
523
524
525 $archive = new \PclZip($file);
526 // extract all files in one folder - the callback functions
527 // (tvr_microthemer_getOnlyValid)
528 // have to be external to this class
529 if ($archive->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REMOVE_ALL_PATH,
530 PCLZIP_CB_PRE_EXTRACT, '\Microthemer\tvr_microthemer_getOnlyValid') == 0) {
531
532
533 $this->log(
534 esc_html__('Extract zip error', 'microthemer'),
535 '<p>' . esc_html__('Error : ', 'microthemer') . $archive->errorInfo(true).'</p>'
536 );
537
538 return false;
539 }
540
541 return true;
542 }
543
544 // handle zip archiving
545 function create_zip($path_to_dir, $dir_name, $zip_store) {
546 $error = false;
547 if( !class_exists('PclZip')) {
548 require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
549 }
550 // check if the /zip-exports dir is writable first
551 if (is_writable($zip_store)) {
552 $archive = new \PclZip($zip_store.$dir_name.'.zip');
553 // create zip
554 $v_list = $archive->create($path_to_dir.$dir_name,
555 PCLZIP_OPT_REMOVE_PATH, $path_to_dir);
556
557 if ($v_list == 0) {
558 $error = true;
559 $this->log(
560 esc_html__('Create zip error', 'microthemer'),
561 '<p>' . esc_html__('Error : ', 'microthemer') . $archive->errorInfo(true).'</p>'
562 );
563 }
564 else {
565 $this->log(
566 esc_html__('Zip package created', 'microthemer'),
567 '<p>' . esc_html__('Zip package successfully created.', 'microthemer') .
568 '<a href="'.$this->thispluginurl.'zip-exports/'.$dir_name.'.zip">' .
569 esc_html__('Download zip file', 'microthemer') . '</a>
570 </p>',
571 'notice'
572 );
573 }
574 }
575 else {
576 $error = true;
577 $this->log(
578 esc_html__('Zip store error', 'microthemer'),
579 '<p>' . sprintf(
580 /* translators: %s: directory path */
581 esc_html__('The directory %s is not writable.', 'microthemer'),
582 $this->root_rel($zip_store)
583 ) . $this->permissionshelp . '</p>'
584 );
585 }
586 // verdict
587 if ($error){
588 return false;
589 } else {
590 return true;
591 }
592 }
593
594 // Get the URL for the downloadable microloader plugin zip.
595 // Premium builds bundle includes/inactive-loading/microloader.zip, but the wp.org
596 // build ships the microloader as a loose directory (zip files are not permitted
597 // in the hosted plugin), so we generate the zip on demand in /micro-themes.
598 function microloader_zip_url(){
599
600 $bundledZip = $this->thisplugindir . 'includes/inactive-loading/microloader.zip';
601
602 if (file_exists($bundledZip)){
603 return $this->thispluginurl . 'includes/inactive-loading/microloader.zip';
604 }
605
606 $sourceDir = $this->thisplugindir . 'includes/inactive-loading/microloader';
607 $zipFile = $this->micro_root_dir . 'microloader.zip';
608
609 if (!file_exists($zipFile)){
610
611 if (!is_dir($sourceDir) || !wp_mkdir_p($this->micro_root_dir)){
612 return false;
613 }
614
615 if (!class_exists('PclZip')) {
616 require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
617 }
618
619 $archive = new \PclZip($zipFile);
620 $v_list = $archive->create($sourceDir,
621 PCLZIP_OPT_REMOVE_PATH, dirname($sourceDir));
622
623 if ($v_list == 0){
624 return false;
625 }
626 }
627
628 return $this->micro_root_url . 'microloader.zip';
629 }
630
631 /*function getDirectoryFileList($directory){
632 return array_diff(scandir($directory), array('..', '.'));
633 }*/
634
635 // Get a flat list of relative paths of files in directory and sub-directory
636 function getDirectoryFileList($directory, $relativePath = '') {
637 $fileList = array();
638 $items = scandir($directory);
639
640 foreach ($items as $item) {
641 if ($item === '.' || $item === '..') {
642 continue;
643 }
644
645 $dir_sep = '/'; // DIRECTORY_SEPARATOR
646 $fullPath = $directory . $dir_sep . $item;
647 $currentPath = ltrim($relativePath . $dir_sep . $item, $dir_sep);
648
649 if (is_dir($fullPath)) {
650 $fileList = array_merge($fileList, $this->getDirectoryFileList($fullPath, $currentPath));
651 } else {
652 $fileList[] = $currentPath;
653 }
654 }
655
656 return $fileList;
657 }
658
659 function maybeLoadMinify(){
660
661 if (!class_exists('Microthemer\Dependencies\Minify')){
662
663 $path = dirname(__FILE__) . '/Dependencies/Minify';
664 require_once $path . '/minify/src/Minify.php';
665 require_once $path . '/minify/src/CSS.php';
666 require_once $path . '/minify/src/JS.php';
667 require_once $path . '/minify/src/Exception.php';
668 require_once $path . '/minify/src/Exceptions/BasicException.php';
669 require_once $path . '/minify/src/Exceptions/FileImportException.php';
670 require_once $path . '/minify/src/Exceptions/IOException.php';
671 require_once $path . '/path-converter/src/ConverterInterface.php';
672 require_once $path . '/path-converter/src/Converter.php';
673 }
674
675 }
676
677 function minify($data = null, $type = 'css', $file = false){
678
679 $this->maybeLoadMinify();
680
681 // setup minifier for CSS or JavaScript
682 if ($type === 'css'){
683 $minifier = new Minify\CSS($data);
684 } else {
685 $minifier = new Minify\JS($data);
686 }
687
688 // return minified data or write directly to a file
689 if ($file){
690 $minifier->minify($file);
691 return true;
692 } else {
693 return $minifier->minify();
694 }
695
696 }
697
698 // write to a file
699 // the file will be created if it doesn't exist. Otherwise, it is overwritten.
700 function write_file($file, $data, $minify = false, $dataType = false, $deleteFirst = false){
701
702 // Ensure the directory exists
703 $dir = dirname($file);
704 if (!is_dir($dir) && !mkdir($dir, 0775, true)) {
705 $this->log(
706 esc_html__('Directory creation error', 'microthemer'),
707 '<p>' . sprintf(
708 /* translators: %s: directory path */
709 esc_html__('Failed to create directory: %s', 'microthemer'),
710 $this->root_rel($dir)) . '</p>'
711 );
712 return false;
713 }
714
715 if ($deleteFirst && file_exists($file)){
716 unlink($file);
717 }
718
719 $write_file = fopen($file, 'w');
720 $write_data = &$data;
721
722 // perhaps we minify
723 if ($minify){
724 $write_data = $this->minify($data, $dataType);
725 }
726
727 // if write is unsuccessful for some reason
728 if (false === fwrite($write_file, $write_data)) {
729
730 fclose($write_file);
731
732 $this->log(
733 esc_html__('File write error', 'microthemer'),
734 '<p>' . sprintf(
735 /* translators: %s: file path */
736 esc_html__('Writing to %s failed.', 'microthemer'),
737 $this->root_rel($file)) . $this->permissionshelp.'</p>'
738 );
739
740 return false;
741 }
742
743 fclose($write_file);
744
745 return true;
746 }
747
748
749
750
751 }
752 // phpcs:enable
753