skinr.module 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  1. <?php
  2. /**
  3. * @file
  4. * Handles core Skinr functionality.
  5. */
  6. /**
  7. * The Skinr API version.
  8. */
  9. define('SKINR_VERSION', 2);
  10. /**
  11. * Show this rule on every page except the listed pages.
  12. */
  13. define('SKINR_RULE_VISIBILITY_NOTLISTED', 0);
  14. /**
  15. * Show this rule on only the listed pages.
  16. */
  17. define('SKINR_RULE_VISIBILITY_LISTED', 1);
  18. /**
  19. * Show this rule if the associated PHP code returns TRUE.
  20. */
  21. define('SKINR_RULE_VISIBILITY_PHP', 2);
  22. /**
  23. * Implements hook_help().
  24. */
  25. function skinr_help($path, $arg) {
  26. switch ($path) {
  27. case 'admin/help#skinr':
  28. if (module_exists('advanced_help')) {
  29. return t('Visit the <a href="@skinr-help">help page</a> for full documentation.', array('@skinr-help' => url('admin/advanced_help/skinr')));
  30. }
  31. else {
  32. return t('Please download and enable the <a href="http://drupal.org/project/advanced_help">Advanced Help</a> module for full Skinr documentation.');
  33. }
  34. break;
  35. }
  36. }
  37. /**
  38. * Implements hook_hook_info().
  39. */
  40. function skinr_hook_info() {
  41. $hooks = array(
  42. 'skinr_api_2',
  43. 'skinr_elements',
  44. 'skinr_group_info',
  45. 'skinr_group_info_alter',
  46. 'skinr_skin_info',
  47. 'skinr_skin_info_alter',
  48. 'skinr_theme_hooks',
  49. 'skinr_theme_hooks_alter',
  50. );
  51. $hooks = array_fill_keys($hooks, array(
  52. 'group' => 'skinr',
  53. ));
  54. return $hooks;
  55. }
  56. /**
  57. * Clears cached Skinr information.
  58. */
  59. function skinr_cache_reset() {
  60. cache_clear_all('skinr_', 'cache', TRUE);
  61. }
  62. /**
  63. * Implements hook_preprocess().
  64. *
  65. * @todo Optimize this function by removing dependencies on
  66. * skinr_get_skin_info() and similar resource heavy functions.
  67. * @todo Account for Drupal's caching being enabled and make it work.
  68. */
  69. function skinr_preprocess(&$variables, $hook) {
  70. // Fix for update script.
  71. if ($hook == 'maintenance_page') {
  72. return;
  73. }
  74. $current_theme = skinr_current_theme();
  75. $skin_info = skinr_get_skin_info();
  76. $theme_registry = theme_get_registry();
  77. $original_hook = (isset($theme_registry[$hook]['original hook']) ? $theme_registry[$hook]['original hook'] : $hook);
  78. // An array of $elements based on $module and $original_hook, derived from $variables.
  79. $array_elements = skinr_invoke_all('skinr_elements', $variables, $original_hook, 'preprocess');
  80. foreach ($array_elements as $module => $elements) {
  81. if (empty($elements)) {
  82. // We can receive empty arrays; if that happens, there's no point
  83. // in continuing.
  84. continue;
  85. }
  86. // Get a list of skin configuration IDs to pass to
  87. // skinr_skin_load_multiple().
  88. $params = array(
  89. 'theme' => $current_theme,
  90. 'module' => $module,
  91. 'element' => $elements,
  92. 'status' => 1,
  93. );
  94. $sids = skinr_skin_get_sids($params);
  95. if (empty($sids)) {
  96. // Noting to apply.
  97. continue;
  98. }
  99. $applied_skins = array();
  100. foreach (skinr_skin_load_multiple($sids) as $skin) {
  101. $applied_skins = array($skin->skin => $skin->options) + $applied_skins;
  102. }
  103. // Invoke hook_skinr_preprocess_alter() in all modules.
  104. // @todo Review whether this alter hook is useful or not, and if it's in
  105. // the right place or not.
  106. $context = array(
  107. 'hook' => $hook,
  108. 'variables' => &$variables,
  109. 'theme' => $current_theme,
  110. 'module' => $module,
  111. 'elements' => $elements,
  112. 'options' => $applied_skins,
  113. );
  114. drupal_alter('skinr_preprocess', $context);
  115. // Use drupal_process_attached() to add attachements such as JS and CSS.
  116. if (!empty($applied_skins)) {
  117. foreach ($applied_skins as $skin_name => $skin_options) {
  118. // Special case for _additional.
  119. if ($skin_name == '_additional') {
  120. continue;
  121. }
  122. // Make sure this skin is enabled for the current theme.
  123. if (isset($skin_info[$skin_name]['attached'])) {
  124. $elements['#attached'] = $skin_info[$skin_name]['attached'];
  125. drupal_process_attached($elements);
  126. }
  127. if (!is_array($skin_options)) {
  128. $skin_options = array($skin_options);
  129. }
  130. foreach ($skin_options as $skin_option) {
  131. if (isset($skin_info[$skin_name]['options'][$skin_option]['attached'])) {
  132. $elements['#attached'] = $skin_info[$skin_name]['options'][$skin_option]['attached'];
  133. drupal_process_attached($elements);
  134. }
  135. }
  136. }
  137. $variables['classes_array'] = array_merge($variables['classes_array'], skinr_flatten_skins_array($applied_skins));
  138. }
  139. }
  140. }
  141. /**
  142. * Returns an array of classes.
  143. *
  144. * @param $skin_options
  145. * An array of skin options keyed by their skin name. The key '_additional'
  146. * is reserved for additional classes entered by the user.
  147. *
  148. * @todo Optimize this function by removing dependencies on the resource heavy
  149. * skinr_get_skin_info() function.
  150. * @todo Rename function to reflect new functionality.
  151. */
  152. function skinr_flatten_skins_array($skin_options) {
  153. $skin_info = skinr_get_skin_info();
  154. $classes = array();
  155. foreach ($skin_options as $skin_name => $options) {
  156. if ($skin_name == '_additional') {
  157. $classes = array_merge($classes, $options);
  158. }
  159. else {
  160. foreach ($options as $option) {
  161. if (!empty($skin_info[$skin_name]['options'][$option]['class'])) {
  162. $classes = array_merge($classes, $skin_info[$skin_name]['options'][$option]['class']);
  163. }
  164. }
  165. }
  166. }
  167. return array_unique($classes);
  168. }
  169. // ------------------------------------------------------------------
  170. // Rule functions.
  171. /**
  172. * Validate a rule object.
  173. *
  174. * @param $rule
  175. * A rule object.
  176. *
  177. * @return
  178. * TRUE on success, FALSE on failure.
  179. */
  180. function skinr_rule_validate(&$rule) {
  181. if (empty($rule->title) || empty($rule->rule_type)) {
  182. return FALSE;
  183. }
  184. if (!isset($rule->node_types)) {
  185. $rule->node_types = array();
  186. }
  187. if (!isset($rule->roles)) {
  188. $rule->roles = array();
  189. }
  190. if (!isset($rule->visibility)) {
  191. $rule->visibility = 0;
  192. }
  193. if (!isset($rule->pages)) {
  194. $rule->pages = '';
  195. }
  196. if (!is_array($rule->node_types) || !is_array($rule->roles)) {
  197. return FALSE;
  198. }
  199. if ($rule->visibility !== 0 && $rule->visibility !== 1 && $rule->visibility !== 2) {
  200. return FALSE;
  201. }
  202. return TRUE;
  203. }
  204. /**
  205. * Save a skinr rule object.
  206. *
  207. * @param $rule
  208. * A rule object.
  209. *
  210. * @return
  211. * The rule ID.
  212. */
  213. function skinr_rule_save($rule) {
  214. // Make sure we're getting valid data.
  215. if (!skinr_rule_validate($rule)) {
  216. return FALSE;
  217. }
  218. $status = drupal_write_record('skinr_rules', $rule, !empty($rule->rid) ? array('rid') : array());
  219. return $status;
  220. }
  221. /**
  222. * Load a skinr rule object.
  223. *
  224. * @param $rid
  225. * (optional) The rule ID.
  226. *
  227. * @return
  228. * A rule object. If no $rid is specified an array of all rules will be
  229. * returned.
  230. */
  231. function skinr_rule_load($rid = NULL) {
  232. $rids = (isset($rid) ? array($rid) : array());
  233. $rules = skinr_rule_load_multiple($rids);
  234. return $rules ? reset($rules) : FALSE;
  235. }
  236. /**
  237. * Loads multiple skinr rule objects.
  238. *
  239. * @param $rids
  240. * An array of rule IDs. Optional.
  241. * @param $conditions
  242. * An array of conditions on the {skinr_rules} table in the form 'field' =>
  243. * $value.
  244. *
  245. * @return
  246. * An array of rule objects indexed by rid. If $rids is not provided, all
  247. * rules are returned.
  248. */
  249. function skinr_rule_load_multiple($rids = array(), $conditions = array()) {
  250. $rules = array();
  251. $select = db_select('skinr_rules')->fields('skinr_rules');
  252. if (!empty($rids)) {
  253. $select->condition('rid', $rids);
  254. }
  255. foreach ($conditions as $field => $condition) {
  256. $select->condition($field, $condition);
  257. }
  258. foreach ($select->execute() as $rule) {
  259. $rule->node_types = unserialize($rule->node_types);
  260. $rule->roles = unserialize($rule->roles);
  261. $rules[$rule->rid] = $rule;
  262. }
  263. return $rules;
  264. }
  265. /**
  266. * Delete a skinr rule object.
  267. *
  268. * @param $rid
  269. * The rule ID.
  270. */
  271. function skinr_rule_delete($rid) {
  272. if ($rule = skinr_rule_load($rid)) {
  273. db_delete('skinr_rules')
  274. ->condition('rid', $rule->rid)
  275. ->execute();
  276. db_delete('skinr_skins')
  277. ->condition('module', 'page')
  278. ->condition('element', $rule->rid)
  279. ->execute();
  280. }
  281. }
  282. /**
  283. * Determines if the rule should be visible for a given path.
  284. *
  285. * @param $rid
  286. * The rule ID.
  287. * @param $path
  288. * (optional) The path to check. Defaults to the path of the current page.
  289. * @param $account
  290. * (optional) The account to check. Defaults to currently logged in user.
  291. *
  292. * @return
  293. * TRUE if the rule should be visible, FALSE otherwise.
  294. */
  295. function skinr_rule_is_visible($rid, $path = NULL, $account = NULL) {
  296. global $user;
  297. if (!isset($account)) {
  298. $account = $user;
  299. }
  300. if ($rule = skinr_rule_load($rid)) {
  301. if (!isset($path)) {
  302. $path = $_GET['q'];
  303. }
  304. // Check role visibility.
  305. if (!empty($rule->roles) && ($account->uid != 1) && !count(array_intersect(array_keys($account->roles), $rule->roles))) {
  306. return FALSE;
  307. }
  308. // Check content type visibility.
  309. // If a rule has no node types associated, it is displayed for every type.
  310. // For rules with node types associated, if the node type does not match
  311. // the settings from this rule, return FALSE.
  312. if (!empty($rule->node_types)) {
  313. $node = menu_get_object('node', 1, $path);
  314. $node_types = node_type_get_types();
  315. if (arg(0, $path) == 'node' && arg(1, $path) == 'add' && arg(2, $path)) {
  316. $node_add_arg = strtr(arg(2, $path), '-', '_');
  317. }
  318. if (!empty($node)) {
  319. // This is a node or node edit page.
  320. if (empty($rule->node_types[$node->type])) {
  321. // This rule should not be displayed for this node type.
  322. return FALSE;
  323. }
  324. }
  325. elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
  326. // This is a node creation page.
  327. if (!isset($rule->node_types[$node_add_arg]) || !$rule->node_types[$node_add_arg]) {
  328. // This rule should not be displayed for this node type.
  329. return FALSE;
  330. }
  331. }
  332. else {
  333. // This is not a node page, remove the rule.
  334. return FALSE;
  335. }
  336. }
  337. // Match path if necessary.
  338. if ($rule->pages) {
  339. // Convert path to lowercase. This allows comparison of the same path
  340. // with different case. Ex: /Page, /page, /PAGE.
  341. $pages = drupal_strtolower($rule->pages);
  342. if ($rule->visibility < SKINR_RULE_VISIBILITY_PHP) {
  343. // Convert the Drupal path to lowercase
  344. $path = drupal_strtolower(drupal_get_path_alias($path));
  345. // Compare the lowercase internal and lowercase path alias (if any).
  346. $page_match = drupal_match_path($path, $pages);
  347. if ($path != $_GET['q']) {
  348. $page_match = $page_match || drupal_match_path($path, $pages);
  349. }
  350. // When $rule->visibility has a value of 0 (SKINR_RULE_VISIBILITY_NOTLISTED),
  351. // the rule is displayed on all pages except those listed in $rule->pages.
  352. // When set to 1 (SKINR_RULE_VISIBILITY_LISTED), it is displayed only on those
  353. // pages listed in $rule->pages.
  354. $page_match = !($rule->visibility xor $page_match);
  355. }
  356. elseif (module_exists('php')) {
  357. $page_match = php_eval($rule->pages);
  358. }
  359. else {
  360. $page_match = FALSE;
  361. }
  362. }
  363. else {
  364. $page_match = TRUE;
  365. }
  366. return $page_match;
  367. }
  368. return FALSE;
  369. }
  370. /**
  371. * Returns a list of extensions that implement this API version of Skinr.
  372. *
  373. * @return
  374. * An associative array whose keys are system names of extensions and whose
  375. * values are again associative arrays containing:
  376. * - type: Either 'module' or 'theme'.
  377. * - name: The system name of the extension.
  378. * - path: The path to the extension.
  379. * - directory: (optional) The sub-directory holding Skinr plugin files.
  380. * - ...: Any other properties defined by the module or theme.
  381. */
  382. function skinr_implements_api() {
  383. $cache = &drupal_static(__FUNCTION__);
  384. if (!isset($cache)) {
  385. if ($cached = cache_get('skinr_implements_api')) {
  386. $cache = $cached->data;
  387. return $cache;
  388. }
  389. $cache = array();
  390. // Collect hook_skinr_api_VERSION() module implementations. This will also
  391. // auto-load $module.skinr.inc files, which may contain skin/group hook
  392. // implementations (when not using the plugin system).
  393. $module_info = system_get_info('module');
  394. foreach (module_implements('skinr_api_' . SKINR_VERSION) as $module) {
  395. // Ensure that $module and the extension type is registered.
  396. $cache[$module] = array(
  397. 'type' => 'module',
  398. 'name' => $module,
  399. 'version' => isset($module_info[$module]['version']) ? $module_info[$module]['version'] : NULL,
  400. );
  401. // Check whether the hook returns any information.
  402. $function = $module . '_skinr_api_' . SKINR_VERSION;
  403. $info = $function();
  404. if (isset($info) && is_array($info)) {
  405. $cache[$module] += $info;
  406. }
  407. // If the module specified a custom path, check whether it contains a
  408. // $module.skinr.inc file and auto-load it. module_implements() only
  409. // auto-loads $module.skinr.inc in a module's root folder.
  410. if (isset($cache[$module]['path'])) {
  411. $file = $cache[$module]['path'] . '/' . $module . '.skinr.inc';
  412. if (file_exists(DRUPAL_ROOT . '/' . $file)) {
  413. $cache[$module]['include file'] = $file;
  414. }
  415. }
  416. // Populate defaults.
  417. $cache[$module] += array(
  418. 'path' => drupal_get_path('module', $module),
  419. 'directory' => NULL,
  420. );
  421. }
  422. // Collect the equivalent of hook_skinr_api_VERSION() implementations in
  423. // themes. The theme system only initializes one theme (and optionally its
  424. // base themes) for the current request, and the phptemplate engine only
  425. // loads template.php during theme initialization. Furthermore, template.php
  426. // is a custom concept of the phptemplate engine and does not exist for
  427. // other theme engines. Since we are interested in all existing
  428. // implementations of all enabled themes, the equivalent of the module hook
  429. // is a theme .info file property 'skinr' that has the sub-keys 'api' and
  430. // optionally 'directory' defined.
  431. // Account for all enabled themes and (any recursive) base themes of them,
  432. // regardless of whether base themes are enabled.
  433. $all_themes = list_themes();
  434. $themes = array();
  435. // Additionally record the base themes and sub themes of each theme, in
  436. // order to apply inheritance rules elsewhere. Do not assign these variables
  437. // as properties on the theme objects themselves, since all objects are
  438. // pointers (much like references) in PHP 5, so our properties would be
  439. // visible for everyone else who calls list_themes().
  440. $base_themes = array();
  441. $sub_themes = array();
  442. foreach ($all_themes as $name => $theme) {
  443. // If the theme is enabled, add it to the stack.
  444. if (!empty($theme->status)) {
  445. $themes[$name] = $theme;
  446. // Find and add all base themes of the enabled theme to the stack.
  447. // @see drupal_theme_initialize()
  448. $sub_theme_name = $name;
  449. while ($name && isset($all_themes[$name]->base_theme)) {
  450. // Record the sub theme for the base theme.
  451. $sub_themes[$all_themes[$name]->base_theme][$name] = $name;
  452. // Add the base theme to the stack.
  453. $name = $all_themes[$name]->base_theme;
  454. $themes[$name] = $all_themes[$name];
  455. // Record the base theme for the original sub theme.
  456. $base_themes[$sub_theme_name][$name] = $name;
  457. }
  458. }
  459. }
  460. foreach ($themes as $name => $theme) {
  461. if (isset($theme->info['skinr']['api']) && $theme->info['skinr']['api'] == SKINR_VERSION) {
  462. // Ensure that the theme name and the extension type is registered.
  463. $cache[$name] = array(
  464. 'type' => 'theme',
  465. 'name' => $name,
  466. 'version' => isset($theme->info['version']) ? $theme->info['version'] : NULL,
  467. 'base themes' => isset($base_themes[$name]) ? $base_themes[$name] : array(),
  468. 'sub themes' => isset($sub_themes[$name]) ? $sub_themes[$name] : array(),
  469. );
  470. // Add any additional information that has been registered.
  471. $cache[$name] += $theme->info['skinr'];
  472. // Populate defaults.
  473. $cache[$name] += array(
  474. 'path' => drupal_get_path('theme', $name),
  475. // Since themes cannot do anything else than registering skins and
  476. // groups, we default to the sub-directory 'skins'.
  477. 'directory' => 'skins',
  478. );
  479. // Lastly, for API consistency with modules, check whether the theme
  480. // contains a $theme.skinr.inc file and auto-load it, if any.
  481. $file = $cache[$name]['path'] . '/' . $name . '.skinr.inc';
  482. if (file_exists(DRUPAL_ROOT . '/' . $file)) {
  483. $cache[$name]['include file'] = $file;
  484. }
  485. }
  486. }
  487. cache_set('skinr_implements_api', $cache);
  488. }
  489. return $cache;
  490. }
  491. /**
  492. * Determine whether a module implements a hook.
  493. *
  494. * Replacement for module_hook() that only invokes modules that implement
  495. * the current version of Skinr API. It also supports $module.skinr.inc files
  496. * in themes and custom paths.
  497. *
  498. * @param $module
  499. * The name of the module (without the .module extension).
  500. * @param $hook
  501. * The name of the hook (e.g. "skinr_skin_info" or "skinr_theme_hooks").
  502. *
  503. * @return
  504. * TRUE if the module is both installed and enabled, and the hook is
  505. * implemented in that module.
  506. */
  507. function skinr_hook($module, $hook) {
  508. $function = $module . '_' . $hook;
  509. if (function_exists($function)) {
  510. return TRUE;
  511. }
  512. // If the hook implementation does not exist, check whether it may live in an
  513. // include file in a custom path.
  514. $extensions = skinr_implements_api();
  515. if (isset($extensions[$module])) {
  516. $extension = $extensions[$module];
  517. if (isset($extension['include file'])) {
  518. // The module specified a custom path. module_hook() only auto-loads
  519. // $module.skinr.inc in a module's root folder.
  520. skinr_load_include($extension['include file']);
  521. if (function_exists($module . '_' . $hook)) {
  522. return TRUE;
  523. }
  524. }
  525. else {
  526. // Run through module_hook() to auto-load $module.skinr.inc from a
  527. // non-custom path.
  528. if (module_hook($module, $hook)) {
  529. return TRUE;
  530. }
  531. }
  532. }
  533. return FALSE;
  534. }
  535. /**
  536. * Determine which modules are implementing a hook.
  537. *
  538. * Replacement for module_implements() that only invokes modules that implement
  539. * the current version of Skinr API. It also supports $module.skinr.inc files
  540. * in themes and custom paths.
  541. *
  542. * @param $hook
  543. * The name of the hook (e.g. "skinr_skin_info" or "skinr_theme_hooks").
  544. *
  545. * @return
  546. * An array with the names of the modules which are implementing this hook.
  547. *
  548. * @see skinr_exit()
  549. */
  550. function skinr_implements($hook) {
  551. $implementations = &drupal_static(__FUNCTION__, array());
  552. // Fetch implementations from cache.
  553. if (empty($implementations)) {
  554. $implementations = cache_get('skinr_implements', 'cache_bootstrap');
  555. if ($implementations === FALSE) {
  556. $implementations = array();
  557. }
  558. else {
  559. $implementations = $implementations->data;
  560. }
  561. }
  562. if (!isset($implementations[$hook])) {
  563. $implementations['#write_cache'] = TRUE;
  564. $extensions = skinr_implements_api();
  565. $implementations[$hook] = array();
  566. foreach ($extensions as $module => $extension) {
  567. if (isset($extension['include file'])) {
  568. // The module specified a custom path. module_implements() and
  569. // module_hook() only auto-load $module.skinr.inc in a module's
  570. // root folder.
  571. $include_file = skinr_load_include($extension['include file']);
  572. if (function_exists($module . '_' . $hook)) {
  573. $implementations[$hook][$module] = $include_file ? $extension['include file'] : FALSE;
  574. }
  575. }
  576. else {
  577. // Run through module_hook() to auto-load $module.skinr.inc from a
  578. // non-custom path.
  579. if (module_hook($module, $hook)) {
  580. $implementations[$hook][$module] = FALSE;
  581. }
  582. }
  583. }
  584. // Allow modules to change the weight of specific implementations but avoid
  585. // an infinite loop.
  586. if ($hook != 'skinr_implements_alter') {
  587. drupal_alter('skinr_implements', $implementations[$hook], $hook);
  588. }
  589. }
  590. else {
  591. foreach ($implementations[$hook] as $module => $file) {
  592. if ($file) {
  593. skinr_load_include($file);
  594. }
  595. else {
  596. module_hook($module, $hook);
  597. }
  598. // It is possible that a module removed a hook implementation without the
  599. // implementations cache being rebuilt yet, so we check whether the
  600. // function exists on each request to avoid undefined function errors.
  601. // Since module_hook() may needlessly try to load the include file again,
  602. // function_exists() is used directly here.
  603. if (!function_exists($module . '_' . $hook)) {
  604. // Clear out the stale implementation from the cache and force a cache
  605. // refresh to forget about no longer existing hook implementations.
  606. unset($implementations[$hook][$module]);
  607. $implementations['#write_cache'] = TRUE;
  608. }
  609. }
  610. }
  611. return array_keys($implementations[$hook]);
  612. }
  613. /**
  614. * Implements hook_exit().
  615. *
  616. * @see module_implements_write_cache()
  617. */
  618. function skinr_exit($destination = NULL) {
  619. $implementations = &drupal_static('skinr_implements');
  620. // Check whether we need to write the cache. We do not want to cache hooks
  621. // which are only invoked on HTTP POST requests since these do not need to be
  622. // optimized as tightly, and not doing so keeps the cache entry smaller.
  623. if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) {
  624. unset($implementations['#write_cache']);
  625. cache_set('skinr_implements', $implementations, 'cache_bootstrap');
  626. }
  627. }
  628. /**
  629. * Invoke a hook in all enabled modules and themes that implement it.
  630. *
  631. * Replacement for module_invoke_all() that only invokes modules that implement
  632. * the current version of Skinr API. It also supports $module.skinr.inc files
  633. * in themes and custom paths.
  634. *
  635. * @param $hook
  636. * The name of the hook to invoke.
  637. * @param ...
  638. * Arguments to pass to the hook.
  639. *
  640. * @return
  641. * An array of return values of the hook implementations. If modules return
  642. * arrays from their implementations, those are merged into one array.
  643. */
  644. function skinr_invoke_all($hook) {
  645. $args = func_get_args();
  646. // Remove $hook from the arguments.
  647. unset($args[0]);
  648. $return = array();
  649. foreach (skinr_implements($hook) as $module) {
  650. $function = $module . '_' . $hook;
  651. if (function_exists($function)) {
  652. $result = call_user_func_array($function, $args);
  653. if (isset($result) && is_array($result)) {
  654. $return = array_merge_recursive($return, $result);
  655. }
  656. elseif (isset($result)) {
  657. $return[] = $result;
  658. }
  659. }
  660. }
  661. return $return;
  662. }
  663. /**
  664. * Loads a $module.skinr.inc include file.
  665. */
  666. function skinr_load_include($file) {
  667. if (is_file($file)) {
  668. include_once $file;
  669. return $file;
  670. }
  671. return FALSE;
  672. }
  673. /**
  674. * Includes Skinr plugin files for an extension, if any.
  675. *
  676. * @param $extension
  677. * The API information for an extension, as returned by skinr_implements_api().
  678. */
  679. function skinr_load_plugins($extension) {
  680. static $loaded = array();
  681. // If plugins have already been loaded for this extension, return them.
  682. if (isset($loaded[$extension['name']])) {
  683. return $loaded[$extension['name']];
  684. }
  685. $loaded[$extension['name']] = array();
  686. // If the extension defines a plugin directory, scan its plugins.
  687. if (isset($extension['directory'])) {
  688. $dir = DRUPAL_ROOT . '/' . $extension['path'] . '/' . $extension['directory'];
  689. $mask = '@^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.inc$@';
  690. $loaded[$extension['name']] = file_scan_directory($dir, $mask, array(
  691. 'key' => 'name',
  692. 'recurse' => TRUE,
  693. 'min_depth' => 1,
  694. 'callback' => 'skinr_include_once',
  695. ));
  696. }
  697. return $loaded[$extension['name']];
  698. }
  699. /**
  700. * file_scan_directory() callback wrapper around include_once.
  701. *
  702. * include_once is a PHP construct, not a function, so it cannot be invoked
  703. * directly as 'callback' in file_scan_directory().
  704. */
  705. function skinr_include_once($file) {
  706. include_once $file;
  707. }
  708. // -----------------------------------------------------------------------
  709. // Skinr data handling functions.
  710. /**
  711. * Validate a skinr object.
  712. *
  713. * @param $skin
  714. * A skin object.
  715. *
  716. * @return
  717. * TRUE on success, FALSE on failure.
  718. */
  719. function skinr_skin_validate(&$skin) {
  720. if (empty($skin->theme) || empty($skin->module) || empty($skin->element) || empty($skin->skin) || empty($skin->options)) {
  721. return FALSE;
  722. }
  723. if (!is_array($skin->options)) {
  724. return FALSE;
  725. }
  726. // Strip empty skins.
  727. $skin->options = _skinr_array_strip_empty($skin->options);
  728. if (empty($skin->options)) {
  729. return FALSE;
  730. }
  731. return TRUE;
  732. }
  733. /**
  734. * Save a skin object.
  735. *
  736. * @param $skin
  737. * A skin object.
  738. *
  739. * @return
  740. * TRUE on success, FALSE on failure.
  741. */
  742. function skinr_skin_save(&$skin) {
  743. // Make sure we're getting valid data.
  744. if (!skinr_skin_validate($skin)) {
  745. return FALSE;
  746. }
  747. // Load the stored skin configuration object, if any.
  748. if (!empty($skin->sid)) {
  749. if (!isset($skin->original)) {
  750. // Load an uncached version of the skin configuration object.
  751. $skin->original = skinr_skin_load_unchanged($skin->sid);
  752. }
  753. }
  754. // Let modules modify the node before it is saved to the database.
  755. module_invoke_all('skinr_skin_presave', $skin);
  756. if (!empty($skin->sid)) {
  757. // Record exists, so let's update.
  758. $status = drupal_write_record('skinr_skins', $skin, 'sid');
  759. module_invoke_all('skinr_skin_update', $skin);
  760. }
  761. else {
  762. // Insert a new record.
  763. $status = drupal_write_record('skinr_skins', $skin);
  764. module_invoke_all('skinr_skin_insert', $skin);
  765. }
  766. // Clear internal properties.
  767. unset($skin->original);
  768. // Clear the static loading cache.
  769. // @todo Once we have a more granular reset for skinr_skin_load_multiple(), we
  770. // need to use it here.
  771. drupal_static_reset('skinr_skin_load_multiple');
  772. return $status;
  773. }
  774. /**
  775. * Delete a skin object.
  776. *
  777. * @param $sid
  778. * The skin configuration ID.
  779. */
  780. function skinr_skin_delete($sid) {
  781. skinr_skin_delete_multiple(array($sid));
  782. }
  783. /**
  784. * Delete multiple skin configuration objects.
  785. *
  786. * @param $sids
  787. * An array of skin configuration IDs.
  788. */
  789. function skinr_skin_delete_multiple($sids) {
  790. $transaction = db_transaction();
  791. if (!empty($sids)) {
  792. $skins = skinr_skin_load_multiple($sids);
  793. try {
  794. foreach ($skins as $sid => $skin) {
  795. module_invoke_all('skinr_skin_delete', $skin);
  796. }
  797. // Delete after calling hooks so that they can query node tables as needed.
  798. db_delete('skinr_skins')
  799. ->condition('sid', $sids, 'IN')
  800. ->execute();
  801. }
  802. catch (Exception $e) {
  803. $transaction->rollback();
  804. watchdog_exception('skinr', $e);
  805. throw $e;
  806. }
  807. // Clear the skinr_skin_load_multiple cache.
  808. drupal_static_reset('skinr_skin_load_multiple');
  809. }
  810. }
  811. /**
  812. * Load a skin configuration object from the database.
  813. *
  814. * @param $sid
  815. * The skin configuration ID.
  816. *
  817. * @return
  818. * A fully-populated skin configuration object.
  819. */
  820. function skinr_skin_load($sid = NULL) {
  821. $sids = (isset($sid) ? array($sid) : array());
  822. $skin = skinr_skin_load_multiple($sids);
  823. return $skin ? reset($skin) : FALSE;
  824. }
  825. /**
  826. * Load skin configuration objects from the database.
  827. *
  828. * This function should be used whenever you need to load more than one skin
  829. * configuration from the database. Skin configurations are loaded into memory
  830. * and will not require database access if loaded again during the same page
  831. * request.
  832. *
  833. * @see skinr_skin_get_sids()
  834. *
  835. * @param $sids
  836. * An array of skin configuration IDs.
  837. *
  838. * @return
  839. * An array of skin configuration objects indexed by sid.
  840. */
  841. function skinr_skin_load_multiple($sids = array()) {
  842. // @todo Do we want to write a more granular cache reset?
  843. $skins = &drupal_static(__FUNCTION__, array());
  844. // Create a new variable which is either a prepared version of the $sids
  845. // array for later comparison with cached skin configuration objects, or FALSE
  846. // if no $sids were passed. The $sids array is reduced as items are loaded
  847. // from cache, and we need to know if it's empty for this reason to avoid
  848. // querying the database when all requested skin configuration objects are
  849. // loaded from cache.
  850. $passed_sids = !empty($sids) ? array_flip($sids) : FALSE;
  851. if ($passed_sids) {
  852. $sids = array_keys(array_diff_key($passed_sids, $skins));
  853. }
  854. // Load any remaining skin configurations from the database. This is the
  855. // case if $sids is set to FALSE (so we load all skins), or if there are any
  856. // sids left to load.
  857. if ($sids === FALSE || $sids) {
  858. // Build the query.
  859. $queried_skins = db_select('skinr_skins', 's')
  860. ->fields('s')
  861. ->condition('sid', $sids)
  862. ->execute()
  863. ->fetchAllAssoc('sid');
  864. foreach ($queried_skins as $sid => $skin) {
  865. // Unserialize options array.
  866. $queried_skins[$sid]->options = unserialize($skin->options);
  867. // Let modules modify the skin configurations.
  868. module_invoke_all('skinr_skin_load', $queried_skins[$sid]);
  869. }
  870. $skins += $queried_skins;
  871. }
  872. // Ensure that the returned array is ordered the same as the original
  873. // $sids array if this was passed in and remove any invalid sids.
  874. if ($passed_sids) {
  875. // Remove any invalid sids from the array.
  876. $passed_sids = array_intersect_key($passed_sids, $skins);
  877. $return = array();
  878. foreach ($passed_sids as $sid => $ignore) {
  879. $return[$sid] = $skins[$sid];
  880. }
  881. }
  882. else {
  883. $return = $skins;
  884. }
  885. return $return;
  886. }
  887. /**
  888. * Load an uncached version of a skin configuration object.
  889. *
  890. * @param $sid
  891. * The skin configuration ID.
  892. *
  893. * @return
  894. * A fully-populated skin configuration object.
  895. */
  896. function skinr_skin_load_unchanged($sid) {
  897. // Load an uncached version of the skin configuration object.
  898. $skin = db_query("SELECT * FROM {skinr_skins} WHERE sid = :sid", array(
  899. ':sid' => $sid,
  900. ))
  901. ->fetchObject();
  902. // Unserialize options array.
  903. $skin->options = unserialize($skin->options);
  904. // Let modules modify the skin configuration.
  905. module_invoke_all('skinr_skin_load', $skin);
  906. return $skin;
  907. }
  908. /**
  909. * Get skin configuration IDs.
  910. *
  911. * @param $filter_by
  912. * An associative array whose keys are:
  913. * - theme: (optional) The theme.
  914. * - module: (optional) The module.
  915. * - element: (optional) The element ID.
  916. * - skin: (optional) The skin name.
  917. * - status: (optional) Boolean indicating whether or not this skin
  918. * configuration is enabled.
  919. *
  920. * @return
  921. * An array of skin configuration IDs.
  922. */
  923. function skinr_skin_get_sids($filter_by = array()) {
  924. $query = db_select('skinr_skins', 's')
  925. ->fields('s', array('sid'));
  926. if (isset($filter_by['theme'])) {
  927. $query->condition('theme', $filter_by['theme']);
  928. }
  929. if (isset($filter_by['module'])) {
  930. $query->condition('module', $filter_by['module']);
  931. }
  932. if (isset($filter_by['element'])) {
  933. $query->condition('element', $filter_by['element']);
  934. }
  935. if (isset($filter_by['skin'])) {
  936. $query->condition('skin', $filter_by['skin']);
  937. }
  938. if (isset($filter_by['status'])) {
  939. $query->condition('status', $filter_by['status']);
  940. }
  941. return $query->execute()
  942. ->fetchCol();
  943. }
  944. /**
  945. * Helper function to remove empty skins from an array.
  946. *
  947. * @param $array
  948. * A single or multi-dimensional array to strip of empty values.
  949. *
  950. * @return
  951. * An array stripped of empty values.
  952. */
  953. function _skinr_array_strip_empty($array) {
  954. $new_array = array();
  955. foreach ($array as $key => $value) {
  956. if (is_array($value)) {
  957. $value = _skinr_array_strip_empty($value);
  958. }
  959. if (!empty($value)) {
  960. $new_array[$key] = $value;
  961. }
  962. }
  963. return $new_array;
  964. }
  965. /**
  966. * Helper function to retrieve the current theme.
  967. *
  968. * The global variable $theme_key doesn't work for our purposes when an admin
  969. * theme is enabled.
  970. *
  971. * @param $exclude_admin_theme
  972. * Optional. Set to TRUE to exclude the admin theme from possible themes to
  973. * return.
  974. *
  975. * @return
  976. * The current theme name.
  977. */
  978. function skinr_current_theme($exclude_admin_theme = FALSE) {
  979. global $user, $custom_theme;
  980. if (!empty($user->theme) && drupal_theme_access($user->theme)) {
  981. $current_theme = $user->theme;
  982. }
  983. elseif (!empty($custom_theme) && drupal_theme_access($custom_theme) && !($exclude_admin_theme && $custom_theme == variable_get('admin_theme', '0'))) {
  984. // Don't return the admin theme if we're editing skinr settings.
  985. $current_theme = $custom_theme;
  986. }
  987. else {
  988. $current_theme = variable_get('theme_default', 'bartik');
  989. }
  990. return $current_theme;
  991. }
  992. /**
  993. * Prepare the default status for a skin.
  994. *
  995. * @param $skin_info
  996. * Information about a registered skin.
  997. *
  998. * @return
  999. * An array of default statuses for each enabled theme.
  1000. */
  1001. function skinr_skin_info_status_default($skin_info) {
  1002. $status = array();
  1003. // Retrieve the explicit default status of the registering theme for itself.
  1004. $base_theme_status = NULL;
  1005. if (isset($skin_info['status'][$skin_info['source']['name']])) {
  1006. $base_theme_status = $skin_info['status'][$skin_info['source']['name']];
  1007. }
  1008. // Retrieve the sub themes of the base theme that registered the skin.
  1009. $sub_themes = array();
  1010. if (isset($skin_info['source']['sub themes'])) {
  1011. $sub_themes = $skin_info['source']['sub themes'];
  1012. }
  1013. $themes = list_themes();
  1014. foreach ($themes as $name => $theme) {
  1015. if (!$theme->status) {
  1016. continue;
  1017. }
  1018. // If this theme is a sub theme of the theme that registered the skin, check
  1019. // whether we need to inherit the status of the base theme to the sub theme.
  1020. // This is the case when a skin of a base theme enables itself for the base
  1021. // theme (not knowing about potential sub themes).
  1022. if (isset($base_theme_status) && isset($sub_themes[$name])) {
  1023. $status[$name] = $base_theme_status;
  1024. }
  1025. // Apply global default.
  1026. $status += array($name => $skin_info['default status']);
  1027. }
  1028. // Lastly, apply all explicit defaults.
  1029. $status = array_merge($status, $skin_info['status']);
  1030. return $status;
  1031. }
  1032. /**
  1033. * Retrieve the overridden status of a skin.
  1034. *
  1035. * @param $skin_info
  1036. * Information about a registered skin.
  1037. *
  1038. * @return
  1039. * An array of statuses for each enabled theme. If no overrides are found,
  1040. * the status defaults will be returned.
  1041. */
  1042. function skinr_skin_info_status_get($skin_info) {
  1043. return variable_get('skinr_skin_' . $skin_info['name'] . '_status', $skin_info['status']);
  1044. }
  1045. /**
  1046. * Set the status of a skin. Overrides the skin plugin settings.
  1047. *
  1048. * @param $skin_info
  1049. * Information about a registered skin.
  1050. * @param $status
  1051. * An array of statuses for each theme.
  1052. */
  1053. function skinr_skin_info_status_set($skin_info, $status) {
  1054. variable_set('skinr_skin_' . $skin_info['name'] . '_status', $status);
  1055. }
  1056. /**
  1057. * Helper function to prepend a path to an array of stylesheet or script filenames.
  1058. *
  1059. * If the url is absolute (e.g. the url start with 'http://' or 'https://')
  1060. * the path does not get prepended.
  1061. *
  1062. * @param $files
  1063. * A an array of filenames that need the path prepended.
  1064. * @todo Adjust docs to account for arrays instead of filenames.
  1065. * @param $path
  1066. * The path to prepend.
  1067. */
  1068. function _skinr_add_path_to_files(&$files, $path) {
  1069. foreach ($files as $key => $file) {
  1070. if (is_array($file)) {
  1071. if (strpos($file[0], 'http://') === 0 || strpos($file[0], 'https://') === 0 ) {
  1072. continue;
  1073. }
  1074. $files[$key][0] = $path . '/' . $file[0];
  1075. }
  1076. else {
  1077. if (strpos($file, 'http://') === 0 || strpos($file, 'https://') === 0) {
  1078. continue;
  1079. }
  1080. $files[$key] = $path . '/' . $file;
  1081. }
  1082. }
  1083. }
  1084. /**
  1085. * Parse a skin_infos array as returned from a skins plugin.
  1086. *
  1087. * This function inserts any missing defaults and updates the stylesheet and
  1088. * script paths to be relative to Drupal's root.
  1089. *
  1090. * @param $skin_infos
  1091. * An array of skins as returned from skin plugins.
  1092. * @param $source
  1093. * An associative array containing information about the source of the skin.
  1094. * See skinr_implements() for details.
  1095. *
  1096. * @todo Merge into skinr_get_skin_info() and remove this function.
  1097. */
  1098. function skinr_skin_info_process(&$skin_infos, $source) {
  1099. foreach ($skin_infos as $skin_name => $skin_info) {
  1100. // Populate default properties.
  1101. $skin_infos[$skin_name] += array(
  1102. 'name' => '',
  1103. 'title' => '',
  1104. 'type' => 'checkboxes',
  1105. 'description' => '',
  1106. 'group' => 'general',
  1107. 'theme hooks' => array('*'),
  1108. 'attached' => array(),
  1109. 'options' => array(),
  1110. 'weight' => NULL,
  1111. 'default status' => 0,
  1112. 'status' => array(),
  1113. );
  1114. // Merge in name.
  1115. $skin_infos[$skin_name]['name'] = $skin_name;
  1116. // Merge in source information.
  1117. $skin_infos[$skin_name]['source'] = $source;
  1118. // Merge in default status for all themes.
  1119. $skin_infos[$skin_name]['status'] = skinr_skin_info_status_default($skin_infos[$skin_name]);
  1120. // Add path to stylesheets.
  1121. if (isset($skin_infos[$skin_name]['attached']['css'])) {
  1122. _skinr_add_path_to_files($skin_infos[$skin_name]['attached']['css'], $source['path']);
  1123. }
  1124. // Add path to scripts.
  1125. if (isset($skin_infos[$skin_name]['attached']['js'])) {
  1126. _skinr_add_path_to_files($skin_infos[$skin_name]['attached']['js'], $source['path']);
  1127. }
  1128. foreach ($skin_infos[$skin_name]['options'] as $option_name => $option) {
  1129. // Add path to stylesheets.
  1130. if (isset($option['attached']['css'])) {
  1131. _skinr_add_path_to_files($skin_infos[$skin_name]['options'][$option_name]['attached']['css'], $source['path']);
  1132. }
  1133. // Add path to scripts.
  1134. if (isset($option['attached']['js'])) {
  1135. _skinr_add_path_to_files($skin_infos[$skin_name]['options'][$option_name]['attached']['js'], $source['path']);
  1136. }
  1137. // Validate class by running it through drupal_html_class().
  1138. if (!is_array($skin_infos[$skin_name]['options'][$option_name]['class'])) {
  1139. $skin_infos[$skin_name]['options'][$option_name]['class'] = array($skin_infos[$skin_name]['options'][$option_name]['class']);
  1140. }
  1141. foreach ($skin_infos[$skin_name]['options'][$option_name]['class'] as $key => $class) {
  1142. $skin_infos[$skin_name]['options'][$option_name]['class'][$key] = drupal_html_class($class);
  1143. }
  1144. }
  1145. }
  1146. }
  1147. /**
  1148. * Retrieves all skins registered by modules and themes.
  1149. *
  1150. * @return
  1151. * An array of skins.
  1152. */
  1153. function skinr_get_skin_info() {
  1154. $skin_info = &drupal_static(__FUNCTION__);
  1155. if (!isset($skin_info)) {
  1156. if ($cached = cache_get('skinr_skin_info')) {
  1157. $skin_info = $cached->data;
  1158. return $skin_info;
  1159. }
  1160. $skin_info = array();
  1161. foreach (skinr_implements_api() as $name => $extension) {
  1162. $hooks = array();
  1163. // Run through skinr_hook to ensure the required include gets loaded.
  1164. if (skinr_hook($name, 'skinr_skin_info')) {
  1165. $hooks["{$name}_skinr_skin_info"] = $extension;
  1166. }
  1167. // Load the extension's plugins, if any.
  1168. if ($files = skinr_load_plugins($extension)) {
  1169. // The base path for plugins is the directory defined by the extension.
  1170. $dir = $extension['path'] . '/' . $extension['directory'];
  1171. foreach ($files as $plugin => $file) {
  1172. $hooks["{$name}_skinr_skin_{$plugin}_info"] = array(
  1173. // The source path for a plugin is the plugin directory.
  1174. 'path' => $dir . '/' . basename(dirname($file->uri)),
  1175. 'filename' => $file->filename,
  1176. ) + $extension;
  1177. }
  1178. }
  1179. foreach ($hooks as $function => $source) {
  1180. if (function_exists($function)) {
  1181. $extension_info = $function();
  1182. if (isset($extension_info) && is_array($extension_info)) {
  1183. // Prepare the skin information.
  1184. skinr_skin_info_process($extension_info, $source);
  1185. $skin_info += $extension_info;
  1186. }
  1187. }
  1188. }
  1189. }
  1190. // Allow modules to alter registered skin information.
  1191. drupal_alter('skinr_skin_info', $skin_info);
  1192. cache_set('skinr_skin_info', $skin_info);
  1193. }
  1194. return $skin_info;
  1195. }
  1196. /**
  1197. * Retrieves all skin groups registered by modules and themes.
  1198. *
  1199. * @return
  1200. * An array of groups.
  1201. */
  1202. function skinr_get_group_info() {
  1203. $group_info = &drupal_static(__FUNCTION__);
  1204. if (!isset($group_info)) {
  1205. if ($cached = cache_get('skinr_group_info')) {
  1206. $group_info = $cached->data;
  1207. return $group_info;
  1208. }
  1209. $group_info = array();
  1210. foreach (skinr_implements_api() as $name => $extension) {
  1211. $hooks = array();
  1212. // Run through skinr_hook to ensure the required include gets loaded.
  1213. if (skinr_hook($name, 'skinr_group_info')) {
  1214. $hooks["{$name}_skinr_group_info"] = $extension;
  1215. }
  1216. // Load the extension's plugins, if any.
  1217. if ($files = skinr_load_plugins($extension)) {
  1218. // The base path for plugins is the directory defined by the extension.
  1219. $dir = $extension['path'] . '/' . $extension['directory'];
  1220. foreach ($files as $plugin => $file) {
  1221. $hooks["{$name}_skinr_group_{$plugin}_info"] = array(
  1222. // The source path for a plugin is the plugin directory.
  1223. 'path' => $dir . '/' . basename(dirname($file->uri)),
  1224. 'filename' => $file->filename,
  1225. ) + $extension;
  1226. }
  1227. }
  1228. foreach ($hooks as $function => $source) {
  1229. if (function_exists($function)) {
  1230. $extension_info = $function();
  1231. if (isset($extension_info) && is_array($extension_info)) {
  1232. // Prepare the skin group information.
  1233. foreach ($extension_info as &$group) {
  1234. $group += array(
  1235. 'title' => '',
  1236. 'description' => '',
  1237. 'weight' => 0,
  1238. );
  1239. }
  1240. $group_info += $extension_info;
  1241. }
  1242. }
  1243. }
  1244. }
  1245. // Allow modules to alter groups through hook_skinr_group_info_alter().
  1246. drupal_alter('skinr_group_info', $group_info);
  1247. cache_set('skinr_group_info', $group_info);
  1248. }
  1249. return $group_info;
  1250. }
  1251. /**
  1252. * Fetch Skinr configuration data from functionality plugins.
  1253. *
  1254. * @return
  1255. * An array of all configuration data.
  1256. */
  1257. function skinr_get_config_info() {
  1258. $config_info = &drupal_static(__FUNCTION__);
  1259. if (!isset($config_info)) {
  1260. if ($cached = cache_get('skinr_config_info')) {
  1261. $config_info = $cached->data;
  1262. return $config_info;
  1263. }
  1264. $config_info = skinr_invoke_all('skinr_config_info');
  1265. // Allow modules to alter config info via hook_skinr_config_info_alter().
  1266. drupal_alter('skinr_config_info', $config_info);
  1267. cache_set('skinr_config_info', $config_info);
  1268. }
  1269. return $config_info;
  1270. }
  1271. /**
  1272. * Provide a list of all available theme hooks for a given element.
  1273. *
  1274. * @param $module
  1275. * The module implementing given element.
  1276. * @param $element
  1277. * An element.
  1278. *
  1279. * @return
  1280. * An array of theme hooks.
  1281. */
  1282. function skinr_theme_hooks($module, $element) {
  1283. $theme_hooks = &drupal_static(__FUNCTION__, array());
  1284. if (!isset($theme_hooks[$module][$element])) {
  1285. // Invoke hook_skinr_theme_hooks() and hook_skinr_theme_hooks_alter().
  1286. $theme_hooks[$module][$element] = skinr_invoke_all('skinr_theme_hooks', $module, $element);
  1287. drupal_alter('skinr_theme_hooks', $theme_hooks[$module][$element], $module, $element);
  1288. }
  1289. return $theme_hooks[$module][$element];
  1290. }
  1291. /**
  1292. * Implements hook_modules_enabled().
  1293. */
  1294. function skinr_modules_enabled() {
  1295. skinr_cache_reset();
  1296. }
  1297. /**
  1298. * Implements hook_modules_disabled().
  1299. */
  1300. function skinr_modules_disabled() {
  1301. skinr_cache_reset();
  1302. }
  1303. /**
  1304. * Implements hook_themes_enabled().
  1305. */
  1306. function skinr_themes_enabled() {
  1307. skinr_cache_reset();
  1308. }
  1309. /**
  1310. * Implements hook_themes_disabled().
  1311. */
  1312. function skinr_themes_disabled() {
  1313. skinr_cache_reset();
  1314. }
  1315. /**
  1316. * Helper function for built-in integration code.
  1317. */
  1318. function skinr_skinr_api_modules() {
  1319. return array(
  1320. 'path' => drupal_get_path('module', 'skinr') . '/modules',
  1321. );
  1322. }
  1323. function block_skinr_api_2() {
  1324. return skinr_skinr_api_modules();
  1325. }
  1326. function comment_skinr_api_2() {
  1327. return skinr_skinr_api_modules();
  1328. }
  1329. function node_skinr_api_2() {
  1330. return skinr_skinr_api_modules();
  1331. }
  1332. function views_skinr_api_2() {
  1333. return skinr_skinr_api_modules();
  1334. }