vc-shortcode-autoloader.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. die( '-1' );
  4. }
  5. /**
  6. * Class VcShortcodeAutoloader
  7. */
  8. class VcShortcodeAutoloader {
  9. private static $instance = null;
  10. private static $config = null;
  11. private static $cached = null;
  12. /**
  13. * @param bool $load_config
  14. * @return \VcShortcodeAutoloader|null
  15. */
  16. public static function getInstance( $load_config = true ) {
  17. if ( null === self::$instance ) {
  18. self::$instance = new VcShortcodeAutoloader( $load_config );
  19. }
  20. return self::$instance;
  21. }
  22. /**
  23. * VcShortcodeAutoloader constructor.
  24. * @param bool $load_config
  25. */
  26. private function __construct( $load_config = true ) {
  27. if ( ! $load_config ) {
  28. return;
  29. }
  30. $config = array(
  31. 'classmap_file' => vc_path_dir( 'APP_ROOT', 'vc_classmap.json.php' ),
  32. 'shortcodes_dir' => vc_path_dir( 'SHORTCODES_DIR' ),
  33. 'root_dir' => vc_path_dir( 'APP_ROOT' ),
  34. );
  35. if ( is_file( $config['classmap_file'] ) ) {
  36. $config['classmap'] = require $config['classmap_file'];
  37. self::$cached = true;
  38. } else {
  39. $config['classmap'] = self::generateClassMap( $config['shortcodes_dir'] );
  40. self::$cached = false;
  41. }
  42. self::$config = $config;
  43. }
  44. /**
  45. * Include class dependencies
  46. *
  47. * @param string $class Class name
  48. *
  49. * @return string[] Included (if any) files
  50. */
  51. public static function includeClass( $class ) {
  52. $class = strtolower( $class );
  53. $files = array();
  54. if ( self::$config['classmap'] ) {
  55. $files = isset( self::$config['classmap'][ $class ] ) ? self::$config['classmap'][ $class ] : array();
  56. }
  57. if ( $files ) {
  58. foreach ( $files as $k => $file ) {
  59. if ( self::$cached ) {
  60. $files[ $k ] = $file = self::$config['root_dir'] . DIRECTORY_SEPARATOR . $file;
  61. }
  62. if ( is_file( $file ) ) {
  63. require_once $file;
  64. }
  65. }
  66. }
  67. return $files;
  68. }
  69. /**
  70. * Find all classes defined in file
  71. *
  72. * @param string $file Full path to file
  73. *
  74. * @return string[]
  75. */
  76. public static function extractClassNames( $file ) {
  77. $classes = array();
  78. // @codingStandardsIgnoreLine
  79. $contents = file_get_contents( $file );
  80. if ( ! $contents ) {
  81. return $classes;
  82. }
  83. $tokens = token_get_all( $contents );
  84. $class_token = false;
  85. foreach ( $tokens as $token ) {
  86. if ( is_array( $token ) ) {
  87. if ( T_CLASS === $token[0] ) {
  88. $class_token = true;
  89. } elseif ( $class_token && T_STRING === $token[0] ) {
  90. $classes[] = $token[1];
  91. $class_token = false;
  92. }
  93. }
  94. }
  95. return $classes;
  96. }
  97. /**
  98. * Extract all classes from file with their extends
  99. *
  100. * @param $file
  101. *
  102. * @return array Associative array where key is class name and value is parent class name (if any))
  103. */
  104. public static function extractClassesAndExtends( $file ) {
  105. $classes = array();
  106. // @codingStandardsIgnoreLine
  107. $contents = file_get_contents( $file );
  108. if ( ! $contents ) {
  109. return $classes;
  110. }
  111. // class Foo extends Bar {
  112. preg_match_all( '/class\s+(\w+)\s+extends\s(\w+)\s+\{/i', $contents, $matches, PREG_SET_ORDER );
  113. foreach ( $matches as $v ) {
  114. $classes[ $v[1] ] = $v[2];
  115. }
  116. // class Foo {
  117. preg_match_all( '/class\s+(\w+)\s+\{/i', $contents, $matches, PREG_SET_ORDER );
  118. foreach ( $matches as $v ) {
  119. $classes[ $v[1] ] = null;
  120. }
  121. return $classes;
  122. }
  123. /**
  124. * Find file by class name
  125. *
  126. * Search is case-insensitive
  127. *
  128. * @param string $class
  129. * @param string[]|string $dirs One or more directories where to look (recursive)
  130. *
  131. * @return string|false Full path to class file
  132. */
  133. public static function findClassFile( $class, $dirs ) {
  134. foreach ( (array) $dirs as $dir ) {
  135. $Directory = new RecursiveDirectoryIterator( $dir );
  136. $Iterator = new RecursiveIteratorIterator( $Directory );
  137. $Regex = new RegexIterator( $Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH );
  138. $class = strtolower( $class );
  139. foreach ( $Regex as $file => $object ) {
  140. $classes = self::extractClassNames( $file );
  141. if ( $classes && in_array( $class, array_map( 'strtolower', $classes ), true ) ) {
  142. return $file;
  143. }
  144. }
  145. }
  146. return false;
  147. }
  148. /**
  149. * Construct full dependency list of classes for each class in right order (including class itself)
  150. *
  151. * @param string[]|string $dirs Directories where to look (recursive)
  152. *
  153. * @return array Associative array where key is lowercase class name and value is array of files to include for
  154. * that class to work
  155. */
  156. public static function generateClassMap( $dirs ) {
  157. $flat_map = array();
  158. foreach ( (array) $dirs as $dir ) {
  159. $Directory = new RecursiveDirectoryIterator( $dir );
  160. $Iterator = new RecursiveIteratorIterator( $Directory );
  161. $Regex = new RegexIterator( $Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH );
  162. foreach ( $Regex as $file => $object ) {
  163. $classes = self::extractClassesAndExtends( $file );
  164. foreach ( $classes as $class => $extends ) {
  165. $class = strtolower( $class );
  166. $extends = strtolower( $extends );
  167. if ( in_array( $extends, array(
  168. 'wpbakeryshortcodescontainer',
  169. 'wpbakeryvisualcomposer',
  170. 'wpbakeryshortcode',
  171. 'wpbmap',
  172. ), true ) ) {
  173. $extends = null;
  174. }
  175. $flat_map[ $class ] = array(
  176. 'class' => $class,
  177. 'file' => $file,
  178. 'extends' => $extends,
  179. );
  180. }
  181. }
  182. }
  183. $map = array();
  184. foreach ( $flat_map as $params ) {
  185. $dependencies = array(
  186. array(
  187. 'class' => $params['class'],
  188. 'file' => $params['file'],
  189. ),
  190. );
  191. if ( $params['extends'] ) {
  192. $queue = array( $params['extends'] );
  193. while ( $queue ) {
  194. $current_class = array_pop( $queue );
  195. $current_class = $flat_map[ $current_class ];
  196. $dependencies[] = array(
  197. 'class' => $current_class['class'],
  198. 'file' => $current_class['file'],
  199. );
  200. if ( ! empty( $current_class['extends'] ) ) {
  201. $queue[] = $current_class['extends'];
  202. }
  203. }
  204. $map[ $params['class'] ] = array_reverse( $dependencies );
  205. } else {
  206. $map[ $params['class'] ] = $dependencies;
  207. }
  208. }
  209. // simplify array
  210. $classmap = array();
  211. foreach ( $map as $class => $dependencies ) {
  212. $classmap[ $class ] = array();
  213. foreach ( $dependencies as $v ) {
  214. $classmap[ $class ][] = str_replace( '\\', '/', $v['file'] );
  215. }
  216. }
  217. return $classmap;
  218. }
  219. /**
  220. * Regenerate and save class map file
  221. *
  222. * @param string[]|string $dirs Directories where to look (recursive)
  223. * @param string $target Output file
  224. *
  225. * @return bool
  226. */
  227. public static function saveClassMap( $dirs, $target ) {
  228. if ( ! $target ) {
  229. return false;
  230. }
  231. $classmap = self::generateClassMap( $dirs );
  232. // @codingStandardsIgnoreLine
  233. $code = '<?php return (array) json_decode(\'' . json_encode( $classmap ) . '\') ?>';
  234. // @codingStandardsIgnoreLine
  235. return (bool) file_put_contents( $target, $code );
  236. }
  237. }