Issue #1984490 by Nick_vh: Added support for OR logic in requirements.
[project/facetapi.git] / facetapi.block.inc
1 <?php
2
3 /**
4 * @file
5 * Block realm code and hook implementations.
6 */
7
8 /**
9 * Implements hook_block_info().
10 *
11 * @see facetapi_get_block_info()
12 */
13 function facetapi_block_info() {
14 return facetapi_get_block_info(array('block'));
15 }
16
17 /**
18 * Helper function to get block info for all block-like realms.
19 *
20 * @param array $realm_names
21 * An array of machine readable realm names blocks are being collected for.
22 *
23 * @return array
24 * An array of block information.
25 *
26 * @see http://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_info/7
27 */
28 function facetapi_get_block_info(array $realm_names) {
29 $blocks = array();
30 foreach ($realm_names as $realm_name) {
31
32 // Gets delta map, iterates over all enabled facets.
33 $map = facetapi_get_delta_map();
34 foreach (facetapi_get_searcher_info() as $searcher => $info) {
35
36 // Gets cache settings for the searcher.
37 $cache = facetapi_get_block_cache_setting($searcher, $realm_name);
38
39 // Adds blocks for facets that are enabled or whose delta mapping is forced.
40 foreach (facetapi_get_delta_map_queue($searcher, $realm_name) as $facet_name) {
41 if ($facet = facetapi_facet_load($facet_name, $searcher)) {
42 // Gets the delta from the delta map.
43 $string = facetapi_build_delta($searcher, $realm_name, $facet_name);
44 $delta = array_search($string, $map);
45
46 // Builds the info. For non-block realm, append the realm name.
47 $subject = 'Facet API: ' . $info['label'] . ' : ' . $facet['label'];
48 if ('block' != $realm_name && ($realm = facetapi_realm_load($realm_name))) {
49 $subject .= ' (' . $realm['label'] . ')';
50 }
51
52 // Defines the block.
53 $blocks[$delta] = array(
54 'info' => $subject,
55 'cache' => $cache,
56 );
57 }
58 }
59 }
60 }
61
62 // Returns available blocks.
63 return $blocks;
64 }
65
66 /**
67 * Implements hook_ctools_block_info().
68 *
69 * @see http://drupal.org/node/1669918
70 */
71 function facetapi_ctools_block_info($module, $delta, &$info) {
72 // Give the Facet API blocks their own category.
73 $info['category'] = t('Facet API');
74 // Allow blocks to be used before the search results in Panels.
75 $info['render last'] = TRUE;
76 }
77
78 /**
79 * Returns block cache settings.
80 *
81 * @param $searcher
82 * The machine readable name of searcher.
83 * @param string $realm_name
84 * The machine readable name of the realm.
85 *
86 * @return int
87 * The constands defined in includes/common.inc, defaults to DRUPAL_NO_CACHE.
88 *
89 * @see includes/common.inc
90 */
91 function facetapi_get_block_cache_setting($searcher, $realm_name) {
92 $name = 'facetapi:block_cache:' . $searcher;
93 if ('block' != $realm_name) {
94 $name .= ':' . $realm_name;
95 }
96 return variable_get($name, DRUPAL_NO_CACHE);
97 }
98
99 /**
100 * Implements hook_block_view().
101 */
102 function facetapi_block_view($delta = '') {
103 $builds = &drupal_static(__FUNCTION__, array());
104 $parsed = &drupal_static('facetapi_parsed_deltas', array());
105
106 // Test block visibility if not already tested.
107 if (!isset($parsed[$delta]) && !facetapi_check_block_visibility($delta)) {
108 return;
109 }
110
111 list($searcher, $realm_name, $facet_name) = $parsed[$delta];
112
113 // Builds and caches the entire realm per searcher / realm combination.
114 $group = $searcher . ':' . $realm_name;
115 if (!isset($builds[$group])) {
116 $builds[$group] = facetapi_build_realm($searcher, $realm_name);
117 }
118
119 // Returns the individual block.
120 if (isset($builds[$group][$facet_name])) {
121
122 // Adds contextual links.
123 $builds[$group][$facet_name]['#contextual_links'] = array(
124 'facetapi' => array('admin/config/search/facetapi', array($searcher, $realm_name, $facet_name)),
125 );
126
127 // Returns the subject and content of the block.
128 $variables = array('title' => $builds[$group][$facet_name]['#title'], 'facet' => $builds[$group][$facet_name]);
129 return array(
130 'subject' => theme('facetapi_title', $variables),
131 'content' => $builds[$group][$facet_name]
132 );
133 }
134 }
135
136 /**
137 * Checks whether the block should be displayed.
138 *
139 * In cases where modules like Context are being used, hook_block_list_alter()
140 * is not invoked and we get fatal errors. We have to test whether or not the
141 * hook has been invoked and call this function manually otherwise.
142 *
143 * @param $delta
144 * The block delta.
145 *
146 * @return
147 * A boolean flagging whether to display this block or not.
148 */
149 function facetapi_check_block_visibility($delta) {
150 $map = facetapi_get_delta_map();
151
152 // Store parsed deltas so we only calculate once. This also lets us know
153 // whether hook_block_list_alter() was called or not.
154 $parsed = &drupal_static('facetapi_parsed_deltas', array());
155
156 // Ensures the delta is mapped.
157 if (!isset($map[$delta])) {
158 $parsed[$delta] = FALSE;
159 return FALSE;
160 }
161
162 // Parses the raw delta, extracts variables for code readability.
163 $parsed[$delta] = facetapi_parse_delta($map[$delta]);
164 list($searcher, $realm_name, $facet_name) = $parsed[$delta];
165
166 // Checks whether block should be displayed.
167 if (!facetapi_is_active_searcher($searcher)) {
168 return FALSE;
169 }
170 if (!facetapi_facet_enabled($searcher, $realm_name, $facet_name)) {
171 return FALSE;
172 }
173 if (!$adapter = facetapi_adapter_load($searcher)) {
174 return FALSE;
175 }
176 if ($adapter->suppressOutput($realm_name)) {
177 return FALSE;
178 }
179
180 // We have facets!
181 return TRUE;
182 }
183
184 /**
185 * Returns a cached delta map of hashes to names.
186 *
187 * Sometimes deltas are longer than 32 chars and need to be passed to hash().
188 * Due to the block table's schema, deltas cannot be longer than 32 characters.
189 * However, hashes are nasty as CSS IDs, so we can use the map to convert
190 * the hashes back to a nicer value in facetapi_preprocess_block().
191 *
192 * @return
193 * An array containing the delta map.
194 */
195 function facetapi_get_delta_map() {
196 $map = &drupal_static(__FUNCTION__);
197 if (NULL === $map) {
198 if ($data = cache_get('facetapi:delta_map') && !empty($data->data)) {
199 $map = $data->data;
200 }
201 else {
202
203 // Maps deltas for all realms. This is a hack since not all realms display
204 // facets in blocks, but it doesn't hurt to store the extra data. The map
205 // is cached anyways, so it shouldn't cause too much additional resource
206 // consumption. It is also an edge-case to have non-block facets.
207 $searcher_info = facetapi_get_searcher_info();
208 foreach (facetapi_get_realm_info() as $realm_name => $realm_info) {
209 foreach ($searcher_info as $searcher => $info) {
210 foreach (facetapi_get_delta_map_queue($searcher, $realm_name) as $facet_name) {
211 $delta = facetapi_build_delta($searcher, $realm_name, $facet_name);
212 $map[facetapi_hash_delta($delta)] = $delta;
213 }
214 }
215 }
216
217 // Caches the map so we don't have to do this repeatedly.
218 cache_set('facetapi:delta_map', $map, 'cache', CACHE_TEMPORARY);
219 }
220 }
221
222 return $map;
223 }
224
225 /**
226 * Build a delta from the searcher, realm name, and facet name.
227 *
228 * @param $searcher
229 * The machine readable name of the searcher.
230 * @param $realm_name
231 * The machine readable name of the realm.
232 * @param $facet_name
233 * The machine readable name of the facet.
234 *
235 * @return
236 * A string containing the raw delta.
237 */
238 function facetapi_build_delta($searcher, $realm_name, $facet_name) {
239 return $searcher . ':' . $realm_name . ':' . urlencode($facet_name);
240 }
241
242 /**
243 * Parses a raw delta into parts.
244 *
245 * @param $raw_delta
246 * A string containing the raw delta prior to being hashed.
247 *
248 * @return
249 * An array containing the searcher, realm_name, and facet name in that order.
250 */
251 function facetapi_parse_delta($raw_delta) {
252 $parsed = array();
253
254 // Splits by ":", finds each part.
255 $parts = explode(':', $raw_delta);
256 $facet_name = array_pop($parts);
257 $facet_name = rawurldecode($facet_name);
258 $realm_name = array_pop($parts);
259 $searcher = implode(':', $parts);
260
261 // Returns array with parsed info.
262 return array($searcher, $realm_name, $facet_name);
263 }
264
265 /**
266 * Hashing code for deltas.
267 *
268 * @param $delta
269 * A string containing the delta.
270 *
271 * @return
272 * The hashed delta value.
273 */
274 function facetapi_hash_delta($delta) {
275 // Ensure hashes are URL safe and alpha-numeric.
276 // @see http://drupal.org/node/1355270
277 $hash = substr(drupal_hash_base64($delta), 0, 32);
278 return strtr($hash, array('-' => '0', '_' => '1'));
279 }
280
281 /**
282 * Returns facets that are enabled or whose delta mapping is forced.
283 *
284 * @param $searcher
285 * The machine readable name of the searcher.
286 * @param $realm_name
287 * The machine readable name of the realm.
288 *
289 * @return array
290 * A list of machine readable facet names.
291 */
292 function facetapi_get_delta_map_queue($searcher, $realm_name) {
293 static $forced;
294 if (NULL === $forced) {
295 $forced = module_invoke_all('facetapi_force_delta_mapping');
296 }
297
298 $enabled = array_keys(facetapi_get_enabled_facets($searcher, $realm_name));
299 if (!isset($forced[$searcher][$realm_name])) {
300 $forced[$searcher][$realm_name] = array();
301 }
302
303 // Merges enabled facets and facets whose mapping is forced.
304 return array_unique(array_merge($enabled, $forced[$searcher][$realm_name]));
305 }