quicktabs.classes.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <?php
  2. /**
  3. * A QuickSet object is an unrendered Quicktabs instance, essentially just a
  4. * container of content items, as defined by its configuration settings and the
  5. * array of content items it contains.
  6. */
  7. class QuickSet {
  8. /**
  9. * The unique name of the QuickSet object.
  10. * This corresponds to the machine name as stored in the database or as defined
  11. * in code.
  12. * @var string
  13. */
  14. protected $name;
  15. /**
  16. * The contents array.
  17. * An array of objects that implement the QuickContentRenderable interface.
  18. * @var array
  19. */
  20. protected $contents;
  21. /**
  22. * An array of settings controlling the behaviour of the QuickSet object. See
  23. * the getDefaultSettings() static function of this class for the full list of
  24. * settings.
  25. * @var array
  26. */
  27. protected $settings;
  28. /**
  29. * Accessors.
  30. */
  31. public function getName() {
  32. return $this->name;
  33. }
  34. public function getContents() {
  35. return $this->contents;
  36. }
  37. public function getSettings() {
  38. return $this->settings;
  39. }
  40. public function getTitle() {
  41. return isset($this->settings['title']) ? $this->translateString($this->settings['title'], 'title') : $this->name;
  42. }
  43. /**
  44. * Instantiate, populate and return a QuickSet object wrapped in a renderer.
  45. *
  46. * @param $name
  47. * The unique name (machine name) of the QuickSet instance.
  48. *
  49. * @param $contents
  50. * The array of content items, each one itself an array with at least a 'type'
  51. * key, a 'title' key, and the other info necessary for that type.
  52. *
  53. * @param $renderer
  54. * The plugin key for this renderer plugin
  55. *
  56. * @param $settings
  57. * An array of settings determining the behaviour of this QuickSet instance.
  58. *
  59. */
  60. public static function QuickSetRendererFactory($name, $contents, $renderer, $settings) {
  61. ctools_include('plugins');
  62. if ($class = ctools_plugin_load_class('quicktabs', 'renderers', $renderer, 'handler')) {
  63. try {
  64. $qs = new self($name, $contents, $settings);
  65. }
  66. catch (InvalidQuickSetException $e) {
  67. watchdog('Quicktabs', $e->getMessage());
  68. return NULL;
  69. }
  70. return new $class($qs);
  71. }
  72. }
  73. /**
  74. * Returns a reference to an object that implements the QuickContentRenderable
  75. * interface.
  76. */
  77. public static function getContentRenderer($tab) {
  78. if ($tab['type'] == 'prerendered') {
  79. return new QuickPreRenderedContent($tab);
  80. }
  81. if ($content = QuickContent::factory($tab['type'], $tab)) {
  82. return $content;
  83. }
  84. return NULL;
  85. }
  86. /**
  87. * Static method to retrieve content from an ajax call. This is called by the
  88. * quicktabs_ajax() callback in quicktabs.module.
  89. */
  90. public static function ajaxRenderContent($type, $args) {
  91. if ($renderer = self::getContentRenderer(array('type' => $type))) {
  92. $output = $renderer->render(FALSE, $args);
  93. return !empty($output) ? drupal_render($output) : '';
  94. }
  95. return '';
  96. }
  97. /**
  98. * Ensure sensible default settings for each QuickSet object.
  99. */
  100. private static function getDefaultSettings() {
  101. return array(
  102. 'title' => '<none>',
  103. 'style' => 'nostyle',
  104. 'hide_empty_tabs' => 0,
  105. 'ajax' => 0,
  106. 'default_tab' => 0,
  107. 'options' => array(),
  108. );
  109. }
  110. /**
  111. * Constructor
  112. */
  113. public function __construct($name, $contents, $settings) {
  114. $this->name = $name;
  115. $this->contents = array();
  116. foreach ($contents as $key => $item) {
  117. // Instantiate a content renderer object and add it to the contents array.
  118. if ($renderer = self::getContentRenderer($item)) {
  119. $this->contents[$key] = $renderer;
  120. }
  121. }
  122. $default_settings = self::getDefaultSettings();
  123. $this->settings = array_merge($default_settings, $settings);
  124. $this->prepareContents();
  125. // Set the default style if necessary.
  126. if ($this->settings['style'] == 'default') {
  127. $this->settings['style'] = variable_get('quicktabs_tabstyle', 'nostyle');
  128. }
  129. }
  130. /**
  131. * Returns an ajax path to be used on ajax-enabled tab links.
  132. *
  133. * @param $index The index of the tab, i.e where it fits into the QuickSet
  134. * instance.
  135. *
  136. * @param $type The type of content we are providing an ajax path for.
  137. */
  138. public function getAjaxPath($index, $type) {
  139. return 'quicktabs/ajax/'. $this->name .'/'. $index . '/'. $type;
  140. }
  141. /**
  142. * Translates Quicktabs user-defined strings if the i18n module is
  143. * enabled.
  144. */
  145. public function translateString($string, $type = 'tab', $index = 0) {
  146. switch ($type) {
  147. case 'tab':
  148. $name = "tab:{$this->name}-{$index}:title";
  149. break;
  150. case 'title':
  151. $name = "title:{$this->name}";
  152. break;
  153. }
  154. return quicktabs_translate($name, $string);
  155. }
  156. /**
  157. * This method does some initial set-up of the tab contents, such as hiding
  158. * tabs with no content if the hide_empty_tabs option is set. It also makes sure
  159. * that prerendered contents are never attempted to be loaded via ajax.
  160. *
  161. * @throws InvalidQuickSetException if there are no contents to render.
  162. */
  163. protected function prepareContents() {
  164. if (!count($this->contents)) {
  165. throw new InvalidQuickSetException('There are no contents to render.');
  166. }
  167. if ($this->settings['hide_empty_tabs'] && !$this->settings['ajax']) {
  168. // Check if any tabs need to be hidden because of empty content.
  169. $renderable_contents = 0;
  170. foreach ($this->contents as $key => $tab) {
  171. $contents = $tab->render(TRUE);
  172. if (empty($contents)) {
  173. // Rather than removing the item, we set it to NULL. This way we retain
  174. // the same indices across tabs, so that permanent links to particular
  175. // tabs can be relied upon.
  176. $this->contents[$key] = NULL;
  177. // The default tab must not be a hidden tab.
  178. if ($this->settings['default_tab'] == $key) {
  179. $this->settings['default_tab'] = ($key + 1) % count($this->contents);
  180. }
  181. }
  182. else {
  183. $renderable_contents++;
  184. }
  185. }
  186. if (!$renderable_contents) {
  187. throw new InvalidQuickSetException('There are no contents to render.');
  188. }
  189. }
  190. elseif ($this->settings['ajax']) {
  191. // Make sure that there is at most 1 prerendered tab and it is the default tab.
  192. // Prerendered content cannot be rendered via ajax.
  193. $has_prerendered = FALSE; // keep track of whether we have found a prerendered tab.
  194. foreach ($this->contents as $key => $tab) {
  195. $type = $tab->getType();
  196. if ($type == 'prerendered') {
  197. if (!$has_prerendered) {
  198. $has_prerendered = TRUE;
  199. $this->settings['default_tab'] = $key;
  200. // In the case of a direct link to a different tab, the 'default_tab'
  201. // will be overridden, so we need to make sure it does not attempt
  202. // to load a pre-rendered tab via ajax. Turn ajax option off.
  203. if ($this->getActiveTab() !== $key) {
  204. $this->settings['ajax'] = 0;
  205. }
  206. }
  207. else {
  208. // We are on a second custom tab and the ajax option is set, we cannot
  209. // render custom tabs via ajax, so we skip out of the loop, set the
  210. // ajax option to off, and call the method again.
  211. $this->settings['ajax'] = 0;
  212. $this->prepareContents();
  213. return;
  214. }
  215. }
  216. }
  217. }
  218. }
  219. /**
  220. * Returns the active tab for a given Quicktabs instance. This could be coming
  221. * from the URL or just from the settings for this instance. If neither, it
  222. * defaults to 0.
  223. */
  224. public function getActiveTab() {
  225. $active_tab = isset($this->settings['default_tab']) ? $this->settings['default_tab'] : key($this->contents);
  226. $active_tab = isset($_GET['qt-' . $this->name]) ? $_GET['qt-' . $this->name] : $active_tab;
  227. $active_tab = (isset($active_tab) && isset($this->contents[$active_tab])) ? $active_tab : 0;
  228. return $active_tab;
  229. }
  230. }
  231. /**
  232. * Abstract base class for QuickSet Renderers.
  233. *
  234. * A renderer object contains a reference to a QuickSet object, which it can
  235. * then render.
  236. */
  237. abstract class QuickRenderer {
  238. /**
  239. * @var QuickSet
  240. */
  241. protected $quickset;
  242. /**
  243. * Constructor
  244. */
  245. public function __construct($quickset) {
  246. $this->quickset = $quickset;
  247. }
  248. /**
  249. * Accessor method for the title.
  250. */
  251. public function getTitle() {
  252. return $this->quickset->getTitle();
  253. }
  254. /**
  255. * The only method that renderer plugins must implement.
  256. *
  257. * @return A render array to be passed to drupal_render().
  258. */
  259. abstract public function render();
  260. /**
  261. * Method for returning the form elements to display for this renderer type on
  262. * the admin form.
  263. * @param $qt An object representing the Quicktabs instance that the tabs are
  264. * being built for.
  265. */
  266. public static function optionsForm($qt) {
  267. return array();
  268. }
  269. }
  270. /*******************************************************
  271. * The classes below relate to individual tab content *
  272. *******************************************************/
  273. /**
  274. * Each QuickSet object has a "contents" property which is an array of objects
  275. * that implement the QuickContentRenderable interface.
  276. */
  277. interface QuickContentRenderable {
  278. /**
  279. * Returns the short type name of the content plugin, e.g. 'block', 'node',
  280. * 'prerendered'.
  281. */
  282. public static function getType();
  283. /**
  284. * Returns the tab title.
  285. */
  286. public function getTitle();
  287. /**
  288. * Returns an array of settings specific to the type of content.
  289. */
  290. public function getSettings();
  291. /**
  292. * Renders the content.
  293. *
  294. * @param $hide_emtpy If set to true, then the renderer should return an empty
  295. * array if there is no content to display, for example if the user does not
  296. * have access to the requested content.
  297. *
  298. * @param $args Used during an ajax call to pass in the settings necessary to
  299. * render this type of content.
  300. */
  301. public function render($hide_empty = FALSE, $args = array());
  302. /**
  303. * Returns an array of keys to use for constructing the correct arguments for
  304. * an ajax callback to retrieve content of this type. The order of the keys
  305. * returned affects the order of the args passed in to the render method when
  306. * called via ajax (see the render() method above).
  307. */
  308. public function getAjaxKeys();
  309. }
  310. /**
  311. * Abstract base class for content plugins.
  312. */
  313. abstract class QuickContent implements QuickContentRenderable {
  314. /**
  315. * Used as the title of the tab.
  316. * @var string
  317. */
  318. protected $title;
  319. /**
  320. * An array containing the information that defines the tab content, specific
  321. * to its type.
  322. * @var array
  323. */
  324. protected $settings;
  325. /**
  326. * A render array of the contents.
  327. * @var array
  328. */
  329. protected $rendered_content;
  330. /**
  331. * Constructor
  332. */
  333. public function __construct($item) {
  334. $this->title = isset($item['title']) ? $item['title'] : '';
  335. // We do not need to store title, type or weight in the settings array, which
  336. // is for type-specific settings.
  337. unset($item['title'], $item['type'], $item['weight']);
  338. $this->settings = $item;
  339. }
  340. /**
  341. * Accessor for the tab title.
  342. */
  343. public function getTitle() {
  344. return $this->title;
  345. }
  346. /**
  347. * Accessor for the tab settings.
  348. */
  349. public function getSettings() {
  350. return $this->settings;
  351. }
  352. /**
  353. * Instantiate a content type object.
  354. *
  355. * @param $name
  356. * The type name of the plugin.
  357. *
  358. * @param $item
  359. * An array containing the item definition
  360. *
  361. */
  362. public static function factory($name, $item) {
  363. ctools_include('plugins');
  364. if ($class = ctools_plugin_load_class('quicktabs', 'contents', $name, 'handler')) {
  365. // We now need to check the plugin's dependencies, to make sure they're installed.
  366. // This info has already been statically cached at this point so there's no
  367. // harm in making a call to ctools_get_plugins().
  368. $plugin = ctools_get_plugins('quicktabs', 'contents', $name);
  369. if (isset($plugin['dependencies'])) {
  370. foreach ($plugin['dependencies'] as $dep) {
  371. // If any dependency is missing we cannot instantiate our class.
  372. if (!module_exists($dep)) return NULL;
  373. }
  374. }
  375. return new $class($item);
  376. }
  377. return NULL;
  378. }
  379. /**
  380. * Method for returning the form elements to display for this tab type on
  381. * the admin form.
  382. *
  383. * @param $delta Integer representing this tab's position in the tabs array.
  384. *
  385. * @param $qt An object representing the Quicktabs instance that the tabs are
  386. * being built for.
  387. */
  388. abstract public function optionsForm($delta, $qt);
  389. }
  390. /**
  391. * This class implements the same interface that content plugins do but it is not
  392. * a content plugin. It is a special class for pre-rendered content which is used
  393. * when "custom" tabs are added to existing Quicktabs instances in a call to
  394. * quicktabs_build_quicktabs().
  395. */
  396. class QuickPreRenderedContent implements QuickContentRenderable {
  397. public static function getType() {
  398. return 'prerendered';
  399. }
  400. /**
  401. * Used as the title of the tab.
  402. * @var title
  403. */
  404. protected $title;
  405. /**
  406. * A render array of the contents.
  407. * @var array
  408. */
  409. protected $rendered_content;
  410. /**
  411. * Constructor
  412. */
  413. public function __construct($item) {
  414. $contents = isset($item['contents']) ? $item['contents'] : array();
  415. if (!is_array($contents)) {
  416. $contents = array('#markup' => $contents);
  417. }
  418. $this->rendered_content = $contents;
  419. $this->title = isset($item['title']) ? $item['title'] : '';
  420. }
  421. /**
  422. * Accessor for the tab title.
  423. */
  424. public function getTitle() {
  425. return $this->title;
  426. }
  427. /**
  428. * Prerendered content doesn't need any extra settings.
  429. */
  430. public function getSettings() {
  431. return array();
  432. }
  433. /**
  434. * The render method simply returns the contents that were passed in and
  435. * stored during construction.
  436. */
  437. public function render($hide_empty = FALSE, $args = array()) {
  438. return $this->rendered_content;
  439. }
  440. /**
  441. * This content cannot be rendered via ajax so we don't return any ajax keys.
  442. */
  443. public function getAjaxKeys() {
  444. return array();
  445. }
  446. }
  447. /**
  448. * Create our own exception class.
  449. */
  450. class InvalidQuickSetException extends Exception {
  451. }