apachesolr.module 102 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032
  1. <?php
  2. /**
  3. * @file
  4. * Integration with the Apache Solr search application.
  5. */
  6. define('APACHESOLR_READ_WRITE', 0);
  7. define('APACHESOLR_READ_ONLY', 1);
  8. define('APACHESOLR_API_VERSION', '3.0');
  9. /**
  10. * Implements hook_init().
  11. */
  12. function apachesolr_init() {
  13. if (arg(0) == 'admin') {
  14. // Add the CSS for this module
  15. drupal_add_css(drupal_get_path('module', 'apachesolr') . '/apachesolr.css');
  16. }
  17. }
  18. /**
  19. * Implements hook_menu().
  20. */
  21. function apachesolr_menu() {
  22. $items = array();
  23. $items['admin/config/search/apachesolr'] = array(
  24. 'title' => 'Apache Solr search',
  25. 'description' => 'Administer Apache Solr.',
  26. 'page callback' => 'apachesolr_status_page',
  27. 'access arguments' => array('administer search'),
  28. 'weight' => -8,
  29. 'file' => 'apachesolr.admin.inc',
  30. );
  31. $items['admin/config/search/apachesolr/index'] = array(
  32. 'title' => 'Default index',
  33. 'description' => 'Administer Apache Solr.',
  34. 'page callback' => 'apachesolr_status_page',
  35. 'access arguments' => array('administer search'),
  36. 'weight' => -8,
  37. 'file' => 'apachesolr.admin.inc',
  38. 'type' => MENU_DEFAULT_LOCAL_TASK,
  39. );
  40. $items['admin/config/search/apachesolr/settings'] = array(
  41. 'title' => 'Settings',
  42. 'weight' => 10,
  43. 'page callback' => 'drupal_get_form',
  44. 'page arguments' => array('apachesolr_settings'),
  45. 'access arguments' => array('administer search'),
  46. 'file' => 'apachesolr.admin.inc',
  47. 'type' => MENU_LOCAL_TASK,
  48. );
  49. $settings_path = 'admin/config/search/apachesolr/settings/';
  50. $items[$settings_path . '%apachesolr_environment/index'] = array(
  51. 'title' => 'Index',
  52. 'page callback' => 'apachesolr_status_page',
  53. 'page arguments' => array(5),
  54. 'access arguments' => array('administer search'),
  55. 'weight' => 0,
  56. 'file' => 'apachesolr.admin.inc',
  57. 'type' => MENU_LOCAL_TASK,
  58. );
  59. $items[$settings_path . '%apachesolr_environment/index/remaining'] = array(
  60. 'title' => 'Remaining',
  61. 'page callback' => 'drupal_get_form',
  62. 'page arguments' => array('apachesolr_index_action_form_remaining_confirm', 5),
  63. 'file' => 'apachesolr.admin.inc',
  64. 'access arguments' => array('administer search'),
  65. 'type' => MENU_CALLBACK,
  66. );
  67. $items[$settings_path . '%apachesolr_environment/index/delete'] = array(
  68. 'title' => 'Reindex',
  69. 'page callback' => 'drupal_get_form',
  70. 'page arguments' => array('apachesolr_index_action_form_delete_confirm', 5),
  71. 'file' => 'apachesolr.admin.inc',
  72. 'access arguments' => array('administer search'),
  73. 'type' => MENU_CALLBACK,
  74. );
  75. $items[$settings_path . '%apachesolr_environment/index/reset'] = array(
  76. 'title' => 'Reindex',
  77. 'page callback' => 'drupal_get_form',
  78. 'page arguments' => array('apachesolr_index_action_form_reset_confirm', 5),
  79. 'file' => 'apachesolr.admin.inc',
  80. 'access arguments' => array('administer search'),
  81. 'type' => MENU_CALLBACK,
  82. );
  83. $items[$settings_path . '%apachesolr_environment/index/reset/confirm'] = array(
  84. 'title' => 'Confirm the re-indexing of all content',
  85. 'page callback' => 'drupal_get_form',
  86. 'page arguments' => array('apachesolr_clear_index_confirm', 5),
  87. 'access arguments' => array('administer search'),
  88. 'file' => 'apachesolr.admin.inc',
  89. 'type' => MENU_CALLBACK,
  90. );
  91. $items[$settings_path . '%apachesolr_environment/index/delete/confirm'] = array(
  92. 'title' => 'Confirm index deletion',
  93. 'page callback' => 'drupal_get_form',
  94. 'page arguments' => array('apachesolr_delete_index_confirm', 5),
  95. 'access arguments' => array('administer search'),
  96. 'file' => 'apachesolr.admin.inc',
  97. 'type' => MENU_CALLBACK,
  98. );
  99. $items[$settings_path . '%apachesolr_environment/edit'] = array(
  100. 'title' => 'Edit',
  101. 'page callback' => 'drupal_get_form',
  102. 'page arguments' => array('apachesolr_environment_edit_form', 5),
  103. 'description' => 'Edit Apache Solr search environment.',
  104. 'access arguments' => array('administer search'),
  105. 'weight' => 10,
  106. 'file' => 'apachesolr.admin.inc',
  107. 'type' => MENU_LOCAL_TASK,
  108. );
  109. $items[$settings_path . '%apachesolr_environment/clone'] = array(
  110. 'title' => 'Apache Solr search environment clone',
  111. 'page callback' => 'drupal_get_form',
  112. 'page arguments' => array('apachesolr_environment_clone_form', 5),
  113. 'access arguments' => array('administer search'),
  114. 'file' => 'apachesolr.admin.inc',
  115. );
  116. $items[$settings_path . '%apachesolr_environment/delete'] = array(
  117. 'title' => 'Apache Solr search environment delete',
  118. 'page callback' => 'drupal_get_form',
  119. 'page arguments' => array('apachesolr_environment_delete_form', 5),
  120. 'access callback' => 'apachesolr_environment_delete_page_access',
  121. 'access arguments' => array('administer search', 5),
  122. 'file' => 'apachesolr.admin.inc',
  123. );
  124. $items[$settings_path . 'add'] = array(
  125. 'title' => 'Add search environment',
  126. 'description' => 'Add Apache Solr environment.',
  127. 'page callback' => 'drupal_get_form',
  128. 'page arguments' => array('apachesolr_environment_edit_form'),
  129. 'access arguments' => array('administer search'),
  130. 'file' => 'apachesolr.admin.inc',
  131. 'type' => MENU_LOCAL_ACTION,
  132. );
  133. $items['admin/config/search/apachesolr/index/confirm/clear'] = array(
  134. 'title' => 'Confirm the re-indexing of all content',
  135. 'page callback' => 'drupal_get_form',
  136. 'page arguments' => array('apachesolr_clear_index_confirm'),
  137. 'access arguments' => array('administer search'),
  138. 'file' => 'apachesolr.admin.inc',
  139. 'type' => MENU_CALLBACK,
  140. );
  141. $items['admin/config/search/apachesolr/index/confirm/delete'] = array(
  142. 'title' => 'Confirm index deletion',
  143. 'page callback' => 'drupal_get_form',
  144. 'page arguments' => array('apachesolr_delete_index_confirm'),
  145. 'access arguments' => array('administer search'),
  146. 'file' => 'apachesolr.admin.inc',
  147. 'type' => MENU_CALLBACK,
  148. );
  149. $reports_path = 'admin/reports/apachesolr';
  150. $items[$reports_path] = array(
  151. 'title' => 'Apache Solr search index',
  152. 'description' => 'Information about the contents of the index on the server',
  153. 'page callback' => 'apachesolr_index_report',
  154. 'access arguments' => array('access site reports'),
  155. 'file' => 'apachesolr.admin.inc',
  156. );
  157. $items[$reports_path . '/%apachesolr_environment'] = array(
  158. 'title' => 'Apache Solr search index',
  159. 'description' => 'Information about the contents of the index on the server',
  160. 'page callback' => 'apachesolr_index_report',
  161. 'page arguments' => array(3),
  162. 'access arguments' => array('access site reports'),
  163. 'file' => 'apachesolr.admin.inc',
  164. );
  165. $items[$reports_path . '/%apachesolr_environment/index'] = array(
  166. 'title' => 'Search index',
  167. 'file' => 'apachesolr.admin.inc',
  168. 'type' => MENU_DEFAULT_LOCAL_TASK,
  169. );
  170. $items[$reports_path . '/%apachesolr_environment/conf'] = array(
  171. 'title' => 'Configuration files',
  172. 'page callback' => 'apachesolr_config_files_overview',
  173. 'access arguments' => array('access site reports'),
  174. 'file' => 'apachesolr.admin.inc',
  175. 'weight' => 5,
  176. 'type' => MENU_LOCAL_TASK,
  177. );
  178. $items[$reports_path . '/%apachesolr_environment/conf/%'] = array(
  179. 'title' => 'Configuration file',
  180. 'page callback' => 'apachesolr_config_file',
  181. 'page arguments' => array(5, 3),
  182. 'access arguments' => array('access site reports'),
  183. 'file' => 'apachesolr.admin.inc',
  184. 'type' => MENU_CALLBACK,
  185. );
  186. if (module_exists('devel')) {
  187. $items['node/%node/devel/apachesolr'] = array(
  188. 'title' => 'Apache Solr',
  189. 'page callback' => 'apachesolr_devel',
  190. 'page arguments' => array(1),
  191. 'access arguments' => array('access devel information'),
  192. 'file' => 'apachesolr.admin.inc',
  193. 'type' => MENU_LOCAL_TASK,
  194. );
  195. }
  196. // We handle our own menu paths for facets
  197. if (module_exists('facetapi')) {
  198. $file_path = drupal_get_path('module', 'facetapi');
  199. $first = TRUE;
  200. foreach (facetapi_get_realm_info() as $realm_name => $realm) {
  201. if ($first) {
  202. $first = FALSE;
  203. $items[$settings_path . '%apachesolr_environment/facets'] = array(
  204. 'title' => 'Facets',
  205. 'page callback' => 'apachesolr_enabled_facets_page',
  206. 'page arguments' => array($realm_name, 5),
  207. 'weight' => -5,
  208. 'access arguments' => array('administer search'),
  209. 'file path' => $file_path,
  210. 'file' => 'facetapi.admin.inc',
  211. 'type' => MENU_LOCAL_TASK,
  212. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  213. );
  214. }
  215. else {
  216. $items[$settings_path . '%apachesolr_environment/facets/' . $realm_name] = array(
  217. 'title' => $realm['label'],
  218. 'page callback' => 'apachesolr_enabled_facets_page',
  219. 'page arguments' => array($realm_name, 5),
  220. 'weight' => -5,
  221. 'access arguments' => array('administer search'),
  222. 'type' => MENU_LOCAL_TASK,
  223. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  224. 'file path' => $file_path,
  225. 'file' => 'facetapi.admin.inc',
  226. );
  227. }
  228. }
  229. }
  230. return $items;
  231. }
  232. /**
  233. * Wrapper for facetapi settings forms.
  234. */
  235. function apachesolr_enabled_facets_page($realm_name, $environment = NULL) {
  236. $page = array();
  237. if (isset($environment['env_id'])) {
  238. $env_id = $environment['env_id'];
  239. }
  240. else {
  241. $env_id = apachesolr_default_environment();
  242. }
  243. $searcher = 'apachesolr@' . $env_id;
  244. // Initializes output with information about which environment's setting we are
  245. // editing, as it is otherwise not transparent to the end user.
  246. $page['apachesolr_environment'] = array(
  247. '#theme' => 'apachesolr_settings_title',
  248. '#env_id' => $env_id,
  249. );
  250. $page['settings'] = drupal_get_form('facetapi_realm_settings_form', $searcher, $realm_name);
  251. return $page;
  252. }
  253. /**
  254. * Implements hook_facetapi_searcher_info().
  255. */
  256. function apachesolr_facetapi_searcher_info() {
  257. $info = array();
  258. // TODO: is it needed to return all of them here?
  259. foreach (apachesolr_load_all_environments() as $id => $environment) {
  260. $info['apachesolr@' . $id] = array(
  261. 'label' => t('Apache Solr environment: @environment', array('@environment' => $environment['name'])),
  262. 'adapter' => 'apachesolr',
  263. 'instance' => $id,
  264. 'path' => '',
  265. 'supports facet mincount' => TRUE,
  266. 'supports facet missing' => TRUE,
  267. 'include default facets' => FALSE,
  268. );
  269. }
  270. return $info;
  271. }
  272. /**
  273. * Implements hook_facetapi_adapters().
  274. */
  275. function apachesolr_facetapi_adapters() {
  276. return array(
  277. 'apachesolr' => array(
  278. 'handler' => array(
  279. 'class' => 'ApacheSolrFacetapiAdapter',
  280. ),
  281. ),
  282. );
  283. }
  284. /**
  285. * Implements hook_facetapi_query_types().
  286. */
  287. function apachesolr_facetapi_query_types() {
  288. return array(
  289. 'apachesolr_term' => array(
  290. 'handler' => array(
  291. 'class' => 'ApacheSolrFacetapiTerm',
  292. 'adapter' => 'apachesolr',
  293. ),
  294. ),
  295. 'apachesolr_date' => array(
  296. 'handler' => array(
  297. 'class' => 'ApacheSolrFacetapiDate',
  298. 'adapter' => 'apachesolr',
  299. ),
  300. ),
  301. 'apachesolr_numeric_range' => array(
  302. 'handler' => array(
  303. 'class' => 'ApacheSolrFacetapiNumericRange',
  304. 'adapter' => 'apachesolr',
  305. ),
  306. ),
  307. 'apachesolr_geo' => array(
  308. 'handler' => array(
  309. 'class' => 'ApacheSolrFacetapiGeo',
  310. 'adapter' => 'apachesolr',
  311. ),
  312. ),
  313. );
  314. }
  315. /**
  316. * Implements hook_facetapi_facet_info().
  317. * Currently it only supports the node entity type
  318. */
  319. function apachesolr_facetapi_facet_info($searcher_info) {
  320. $facets = array();
  321. if ('apachesolr' == $searcher_info['adapter']) {
  322. $environment = apachesolr_environment_load($searcher_info['instance']);
  323. if (!empty($environment['conf']['facet callbacks'])) {
  324. foreach ($environment['conf']['facet callbacks'] as $callback) {
  325. if (is_callable($callback)) {
  326. $facets = array_merge($facets, call_user_func($callback, $searcher_info));
  327. }
  328. }
  329. }
  330. elseif (isset($searcher_info['types']['node'])) {
  331. $facets = apachesolr_default_node_facet_info();
  332. }
  333. }
  334. return $facets;
  335. }
  336. /**
  337. * Returns an array of facets for node fields and attributes.
  338. *
  339. * @return
  340. * An array of node facets.
  341. */
  342. function apachesolr_default_node_facet_info() {
  343. return array_merge(apachesolr_common_node_facets(), apachesolr_entity_field_facets('node'));
  344. }
  345. /**
  346. * Returns an array of facets for the provided entity type's fields.
  347. *
  348. * @param string $entity_type
  349. * An entity type machine name.
  350. * @return
  351. * An array of facets for the fields of the requested entity type.
  352. */
  353. function apachesolr_entity_field_facets($entity_type) {
  354. $facets = array();
  355. foreach (apachesolr_entity_fields($entity_type) as $field_nm => $entity_fields) {
  356. foreach ($entity_fields as $field_info) {
  357. if (!empty($field_info['facets'])) {
  358. $field = apachesolr_index_key($field_info);
  359. $facets[$field] = array(
  360. 'label' => check_plain($field_info['display_name']),
  361. 'dependency plugins' => $field_info['dependency plugins'],
  362. 'field api name' => $field_info['field']['field_name'],
  363. 'description' => t('Filter by field @field of type @type.', array(
  364. '@type' => $field_info['field']['type'],
  365. '@field' => $field_info['field']['field_name'],
  366. )),
  367. 'map callback' => $field_info['map callback'],
  368. 'map options' => $field_info,
  369. 'hierarchy callback' => $field_info['hierarchy callback'],
  370. );
  371. if (!empty($field_info['facet mincount allowed'])) {
  372. $facets[$field]['facet mincount allowed'] = $field_info['facet mincount allowed'];
  373. }
  374. if (!empty($field_info['facet missing allowed'])) {
  375. $facets[$field]['facet missing allowed'] = $field_info['facet missing allowed'];
  376. }
  377. if (!empty($field_info['query types'])) {
  378. $facets[$field]['query types'] = $field_info['query types'];
  379. }
  380. if (!empty($field_info['allowed operators'])) {
  381. $facets[$field]['allowed operators'] = $field_info['allowed operators'];
  382. }
  383. // TODO : This is actually deprecated but we should still support
  384. // older versions of facetapi. We should remove once facetapi has RC1
  385. // For reference : http://drupal.org/node/1161444
  386. if (!empty($field_info['query type'])) {
  387. $facets[$field]['query type'] = $field_info['query type'];
  388. }
  389. if (!empty($field_info['min callback'])) {
  390. $facets[$field]['min callback'] = $field_info['min callback'];
  391. }
  392. if (!empty($field_info['max callback'])) {
  393. $facets[$field]['max callback'] = $field_info['max callback'];
  394. }
  395. if (!empty($field_info['map callback'])) {
  396. $facets[$field]['map callback'] = $field_info['map callback'];
  397. }
  398. if (!empty($field_info['alter callbacks'])) {
  399. $facets[$field]['alter callbacks'] = $field_info['alter callbacks'];
  400. }
  401. }
  402. }
  403. }
  404. return $facets;
  405. }
  406. /**
  407. * Helper function returning common facet definitions.
  408. */
  409. function apachesolr_common_node_facets() {
  410. $facets['bundle'] = array(
  411. 'label' => t('Content type'),
  412. 'description' => t('Filter by content type.'),
  413. 'field api bundles' => array('node'),
  414. 'map callback' => 'facetapi_map_bundle',
  415. 'values callback' => 'facetapi_callback_type_values',
  416. 'facet mincount allowed' => TRUE,
  417. 'dependency plugins' => array('role'),
  418. );
  419. $facets['author'] = array(
  420. 'label' => t('Author'),
  421. 'description' => t('Filter by author.'),
  422. 'field' => 'is_uid',
  423. 'map callback' => 'facetapi_map_author',
  424. 'values callback' => 'facetapi_callback_user_values',
  425. 'facet mincount allowed' => TRUE,
  426. 'dependency plugins' => array('bundle', 'role'),
  427. );
  428. $facets['language'] = array(
  429. 'label' => t('Language'),
  430. 'description' => t('Filter by language.'),
  431. 'field' => 'ss_language',
  432. 'map callback' => 'facetapi_map_language',
  433. 'values callback' => 'facetapi_callback_language_values',
  434. 'facet mincount allowed' => TRUE,
  435. 'dependency plugins' => array('bundle', 'role'),
  436. );
  437. $facets['created'] = array(
  438. 'label' => t('Post date'),
  439. 'description' => t('Filter by the date the node was posted.'),
  440. 'field' => 'ds_created',
  441. 'query types' => array('date'),
  442. 'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE),
  443. 'map callback' => 'facetapi_map_date',
  444. 'min callback' => 'facetapi_get_min_date',
  445. 'max callback' => 'facetapi_get_max_date',
  446. 'dependency plugins' => array('bundle', 'role'),
  447. 'default sorts' => array(
  448. array('active', SORT_DESC),
  449. array('indexed', SORT_ASC),
  450. ),
  451. );
  452. $facets['changed'] = array(
  453. 'label' => t('Updated date'),
  454. 'description' => t('Filter by the date the node was last modified.'),
  455. 'field' => 'ds_changed',
  456. 'query types' => array('date'),
  457. 'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE),
  458. 'map callback' => 'facetapi_map_date',
  459. 'min callback' => 'facetapi_get_min_date',
  460. 'max callback' => 'facetapi_get_max_date',
  461. 'dependency plugins' => array('bundle', 'role'),
  462. 'default sorts' => array(
  463. array('active', SORT_DESC),
  464. array('indexed', SORT_ASC),
  465. ),
  466. );
  467. if (module_exists('book')) {
  468. $facets['book'] = array(
  469. 'label' => t('Book'),
  470. 'description' => t('Filter by the book that the node belongs to.'),
  471. 'field' => 'is_book_bid',
  472. 'map callback' => 'apachesolr_map_book',
  473. 'facet mincount allowed' => TRUE,
  474. 'dependency plugins' => array('bundle', 'role'),
  475. );
  476. }
  477. return $facets;
  478. }
  479. /**
  480. * FacetAPI mapping callback.
  481. */
  482. function apachesolr_map_book(array $values) {
  483. $map = array();
  484. if (!empty($values)) {
  485. foreach (book_get_books() as $bid => $book) {
  486. if (in_array($bid, $values)) {
  487. $map[$bid] = $book['title'];
  488. }
  489. }
  490. }
  491. return $map;
  492. }
  493. /**
  494. * Implements hook_form_[form_id]_alter().
  495. *
  496. * Mark a node for re-indexing when the book outline form is saved.
  497. */
  498. function apachesolr_form_book_outline_form_alter(&$form, $form_state) {
  499. $form['#submit'][] = 'apachesolr_mark_book_outline_node';
  500. }
  501. /**
  502. * Submit handler for the book outline form.
  503. *
  504. * Marks the node for re-indexing.
  505. */
  506. function apachesolr_mark_book_outline_node($form, $form_state) {
  507. apachesolr_mark_entity('node', $form['#node']->nid);
  508. }
  509. /**
  510. * Determines Apache Solr's behavior when searching causes an exception (e.g. Solr isn't available.)
  511. * Depending on the admin settings, possibly redirect to Drupal's core search.
  512. *
  513. * @param $search_name
  514. * The name of the search implementation.
  515. *
  516. * @param $querystring
  517. * The search query that was issued at the time of failure.
  518. */
  519. function apachesolr_failure($search_name, $querystring) {
  520. $fail_rule = variable_get('apachesolr_failure', 'apachesolr:show_error');
  521. switch ($fail_rule) {
  522. case 'apachesolr:show_error':
  523. drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error');
  524. break;
  525. case 'apachesolr:show_no_results':
  526. // Do nothing.
  527. break;
  528. default:
  529. // If we're failing over to another module make sure the search is available.
  530. if (module_exists('search')) {
  531. $search_info = search_get_info();
  532. if (isset($search_info[$fail_rule])) {
  533. $search_info = $search_info[$fail_rule];
  534. drupal_set_message(t("%search_name is not available. Your search is being redirected.", array('%search_name' => $search_name)));
  535. drupal_goto('search/' . $search_info['path'] . '/' . rawurlencode($querystring));
  536. }
  537. }
  538. // if search is not enabled, break and do nothing
  539. break;
  540. }
  541. }
  542. /**
  543. * Like $site_key in _update_refresh() - returns a site-specific hash.
  544. */
  545. function apachesolr_site_hash() {
  546. if (!($hash = variable_get('apachesolr_site_hash', FALSE))) {
  547. global $base_url;
  548. // Set a random 6 digit base-36 number as the hash.
  549. $hash = substr(base_convert(sha1(uniqid($base_url, TRUE)), 16, 36), 0, 6);
  550. variable_set('apachesolr_site_hash', $hash);
  551. }
  552. return $hash;
  553. }
  554. /**
  555. * Generate a unique ID for an entity being indexed.
  556. *
  557. * @param $id
  558. * An id number (or string) unique to this site, such as a node ID.
  559. * @param $entity
  560. * A string like 'node', 'file', 'user', or some other Drupal object type.
  561. *
  562. * @return
  563. * A string combining the parameters with the site hash.
  564. */
  565. function apachesolr_document_id($id, $entity_type = 'node') {
  566. return apachesolr_site_hash() . "/{$entity_type}/" . $id;
  567. }
  568. /**
  569. * Mark one entity as needing re-indexing.
  570. */
  571. function apachesolr_mark_entity($entity_type, $entity_id) {
  572. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  573. $table = apachesolr_get_indexer_table($entity_type);
  574. if (!empty($table)) {
  575. db_update($table)
  576. ->condition('entity_id', $entity_id)
  577. ->fields(array('changed' => REQUEST_TIME))
  578. ->execute();
  579. }
  580. }
  581. /**
  582. * Implements hook_user_update().
  583. *
  584. * Mark nodes as needing re-indexing if the author name changes.
  585. *
  586. * @see http://drupal.org/node/592522
  587. * Performance issue with Mysql
  588. * @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
  589. * To know why PDO in drupal does not support UPDATE and JOIN at once.
  590. */
  591. function apachesolr_user_update(&$edit, $account, $category) {
  592. if (isset($account->name) && isset($account->original) && isset($account->original->name) && $account->name != $account->original->name) {
  593. $table = apachesolr_get_indexer_table('node');
  594. switch (db_driver()) {
  595. case 'mysql' :
  596. $table = db_escape_table($table);
  597. $query = "UPDATE {{$table}} asn
  598. INNER JOIN {node} n ON asn.entity_id = n.nid SET asn.changed = :changed
  599. WHERE n.uid = :uid";
  600. $result = db_query($query, array(':changed' => REQUEST_TIME,
  601. ':uid' => $account->uid,
  602. ));
  603. break;
  604. default :
  605. $nids = db_select('node')
  606. ->fields('node', array('nid'))
  607. ->where("uid = :uid", array(':uid' => $account->uid));
  608. $update = db_update($table)
  609. ->condition('entity_id', $nids, 'IN')
  610. ->fields(array('changed' => REQUEST_TIME))
  611. ->execute();
  612. }
  613. }
  614. }
  615. /**
  616. * Implements hook_term_update().
  617. *
  618. * Mark nodes as needing re-indexing if a term name changes.
  619. *
  620. * @see http://drupal.org/node/592522
  621. * Performance issue with Mysql
  622. * @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
  623. * To know why PDO in drupal does not support UPDATE and JOIN at once.
  624. * @todo the rest, such as term deletion.
  625. */
  626. function apachesolr_taxonomy_term_update($term) {
  627. $table = apachesolr_get_indexer_table('node');
  628. switch (db_driver()) {
  629. case 'mysql' :
  630. $table = db_escape_table($table);
  631. $query = "UPDATE {{$table}} asn
  632. INNER JOIN {taxonomy_index} ti ON asn.entity_id = ti.nid SET asn.changed = :changed
  633. WHERE ti.tid = :tid";
  634. $result = db_query($query, array(':changed' => REQUEST_TIME,
  635. ':tid' => $term->tid,
  636. ));
  637. break;
  638. default :
  639. $nids = db_select('taxonomy_index')
  640. ->fields('taxonomy_index', array('nid'))
  641. ->where("tid = :tid", array(':tid' => $term->tid));
  642. $update = db_update($table)
  643. ->condition('entity_id', $nids, 'IN')
  644. ->fields(array('changed' => REQUEST_TIME))
  645. ->execute();
  646. }
  647. }
  648. /**
  649. * Implement hook_comment_*().
  650. *
  651. * Mark nodes as needing re-indexing if comments are added or changed.
  652. * Like search_comment().
  653. */
  654. /**
  655. * Implements hook_comment_insert().
  656. */
  657. function apachesolr_comment_insert($comment) {
  658. apachesolr_mark_entity('node', $comment->nid);
  659. }
  660. /**
  661. * Implements hook_comment_update().
  662. */
  663. function apachesolr_comment_update($comment) {
  664. apachesolr_mark_entity('node', $comment->nid);
  665. }
  666. /**
  667. * Implements hook_comment_delete().
  668. */
  669. function apachesolr_comment_delete($comment) {
  670. apachesolr_mark_entity('node', $comment->nid);
  671. }
  672. /**
  673. * Implements hook_comment_publish().
  674. */
  675. function apachesolr_comment_publish($comment) {
  676. apachesolr_mark_entity('node', $comment->nid);
  677. }
  678. /**
  679. * Implements hook_comment_unpublish().
  680. */
  681. function apachesolr_comment_unpublish($comment) {
  682. apachesolr_mark_entity('node', $comment->nid);
  683. }
  684. /**
  685. * Implements hook_node_type_delete().
  686. */
  687. function apachesolr_node_type_delete($info) {
  688. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  689. $env_id = apachesolr_default_environment();
  690. $existing_bundles = apachesolr_get_index_bundles($env_id, 'node');
  691. $new_bundles = $existing_bundles;
  692. $index = array_search($info->type, $existing_bundles);
  693. if ($index !== FALSE) {
  694. unset($new_bundles[$index]);
  695. $new_bundles = array_values($new_bundles);
  696. apachesolr_index_set_bundles($env_id, 'node', $new_bundles);
  697. }
  698. apachesolr_index_delete_bundles($env_id, 'node', array($info->type));
  699. $bundles_changed_callback = apachesolr_entity_get_callback('node', 'bundles changed callback');
  700. if (!empty($bundles_changed_callback)) {
  701. call_user_func($bundles_changed_callback, $env_id, $existing_bundles, $new_bundles);
  702. }
  703. apachesolr_environments_clear_cache();
  704. }
  705. /**
  706. * Implements hook_node_type_update().
  707. *
  708. * @see http://drupal.org/node/592522
  709. * Performance issue with Mysql
  710. * @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
  711. * To know why PDO in drupal does not support UPDATE and JOIN at once.
  712. * @todo Support backwards compatibility
  713. */
  714. function apachesolr_node_type_update($info) {
  715. if (!empty($info->old_type) && $info->old_type != $info->type) {
  716. // We cannot be sure we are going before or after node module.
  717. $table = apachesolr_get_indexer_table('node');
  718. switch (db_driver()) {
  719. case 'mysql' :
  720. $table = db_escape_table($table);
  721. $query = "UPDATE {{$table}} asn
  722. INNER JOIN {node} n ON asn.entity_id = n.nid SET asn.changed = :changed
  723. WHERE (n.type = :type OR n.type = :old_type)";
  724. $result = db_query($query, array(':changed' => REQUEST_TIME,
  725. ':type' => $info->type,
  726. ':old_type' => $info->old_type,
  727. ));
  728. break;
  729. default :
  730. $nids = db_select('node')
  731. ->fields('node', array('nid'))
  732. ->where("type = :new OR type = :old", array(':new' => $info->type, ':old' => $info->old_type));
  733. $update = db_update($table)
  734. ->condition('entity_id', $nids, 'IN')
  735. ->fields(array('changed' => REQUEST_TIME))
  736. ->execute();
  737. }
  738. db_update('apachesolr_index_bundles')
  739. ->condition('bundle', $info->old_type)
  740. ->condition('entity_type', 'node')
  741. ->fields(array('bundle' => $info->type))
  742. ->execute();
  743. apachesolr_environments_clear_cache();
  744. }
  745. }
  746. /**
  747. * Implements hook_node_type_insert().
  748. *
  749. * Insert our new type into all the environments as indexable bundle type
  750. * @param array $info
  751. */
  752. function apachesolr_node_type_insert($info) {
  753. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  754. // Get all our environments
  755. $envs = apachesolr_load_all_environments();
  756. $bundles = array();
  757. foreach($envs as $env) {
  758. if (isset($env['index_bundles']['node'])) {
  759. $bundles = $env['index_bundles']['node'];
  760. }
  761. // Is the bundle already marked?
  762. if (!in_array($info->type, $bundles)) {
  763. $bundles[] = $info->type;
  764. // Set the new bundle as indexable for all environments
  765. apachesolr_index_set_bundles($env['env_id'], 'node', $bundles);
  766. }
  767. }
  768. }
  769. /**
  770. * Convert date from timestamp into ISO 8601 format.
  771. * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
  772. */
  773. function apachesolr_date_iso($date_timestamp) {
  774. return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp);
  775. }
  776. /**
  777. * Function to flatten documents array recursively.
  778. *
  779. * @param array $documents
  780. * The array of documents being indexed.
  781. * @param array &$tmp
  782. * A container variable that will contain the flattened array.
  783. */
  784. function apachesolr_flatten_documents_array($documents, &$tmp) {
  785. foreach ($documents AS $index => $item) {
  786. if (is_array($item)) {
  787. apachesolr_flatten_documents_array($item, $tmp);
  788. }
  789. elseif (is_object($item)) {
  790. $tmp[] = $item;
  791. }
  792. }
  793. }
  794. /**
  795. * Implements hook_flush_caches().
  796. */
  797. function apachesolr_flush_caches() {
  798. return array('cache_apachesolr');
  799. }
  800. /**
  801. * A wrapper for cache_clear_all to be used as a submit handler on forms that
  802. * require clearing Luke cache etc.
  803. */
  804. function apachesolr_clear_cache($env_id) {
  805. // Reset $env_id to NULL if call originates from a form submit handler.
  806. if (is_array($env_id)) {
  807. $env_id = NULL;
  808. }
  809. try {
  810. $solr = apachesolr_get_solr($env_id);
  811. $solr->clearCache();
  812. }
  813. catch (Exception $e) {
  814. watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
  815. drupal_set_message(nl2br(check_plain($e->getMessage())), 'warning');
  816. }
  817. }
  818. /**
  819. * Call drupal_set_message() with the text.
  820. *
  821. * The text is translated with t() and substituted using Solr stats.
  822. * @todo This is not according to drupal code standards
  823. */
  824. function apachesolr_set_stats_message($text, $type = 'status', $repeat = FALSE) {
  825. try {
  826. $solr = apachesolr_get_solr();
  827. $stats_summary = $solr->getStatsSummary();
  828. drupal_set_message(check_plain(t($text, $stats_summary)), $type, FALSE);
  829. }
  830. catch (Exception $e) {
  831. watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
  832. }
  833. }
  834. /**
  835. * Returns last changed and last ID for an environment and entity type.
  836. */
  837. function apachesolr_get_last_index_position($env_id, $entity_type) {
  838. $stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
  839. return isset($stored[$entity_type]) ? $stored[$entity_type] : array('last_changed' => 0, 'last_entity_id' => 0);
  840. }
  841. /**
  842. * Sets last changed and last ID for an environment and entity type.
  843. */
  844. function apachesolr_set_last_index_position($env_id, $entity_type, $last_changed, $last_entity_id) {
  845. $stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
  846. $stored[$entity_type] = array('last_changed' => $last_changed, 'last_entity_id' => $last_entity_id);
  847. apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', $stored);
  848. }
  849. /**
  850. * Clear a specific environment, or clear all.
  851. */
  852. function apachesolr_clear_last_index_position($env_id = NULL, $entity_type = NULL) {
  853. if (!empty($env_id)) {
  854. $stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
  855. if ($entity_type) {
  856. unset($stored[$entity_type]);
  857. }
  858. else {
  859. $stored = array();
  860. }
  861. apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', $stored);
  862. }
  863. else {
  864. $environments = apachesolr_load_all_environments();
  865. foreach (array_keys($environments) as $env_id) {
  866. apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', array());
  867. }
  868. }
  869. }
  870. /**
  871. * Set the timestamp of the last index update
  872. * @param $timestamp
  873. * A timestamp or zero. If zero, the variable is deleted.
  874. */
  875. function apachesolr_set_last_index_updated($env_id, $timestamp = 0) {
  876. apachesolr_environment_variable_set($env_id, 'apachesolr_index_updated', $timestamp);
  877. }
  878. /**
  879. * Get the timestamp of the last index update.
  880. * @return integer (timestamp)
  881. */
  882. function apachesolr_get_last_index_updated($env_id) {
  883. return apachesolr_environment_variable_get($env_id, 'apachesolr_index_updated', 0);
  884. }
  885. /**
  886. * Implements hook_cron().
  887. * Runs the indexing process on all writable environments or just a given environment.
  888. */
  889. function apachesolr_cron($env_id = NULL) {
  890. $environments = array();
  891. if (empty($env_id)) {
  892. $environments = array_keys(apachesolr_load_all_environments());
  893. }
  894. else {
  895. $environments[] = $env_id;
  896. }
  897. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  898. // Optimize the index (by default once a day).
  899. $optimize_interval = variable_get('apachesolr_optimize_interval', 60 * 60 * 24);
  900. $time = REQUEST_TIME;
  901. foreach($environments as $env_id) {
  902. $last = apachesolr_environment_variable_get($env_id, 'apachesolr_last_optimize', 0);
  903. // Indexes in read-only mode do not change the index, so will not update, delete, or optimize during cron.
  904. if (apachesolr_environment_variable_get($env_id, 'apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) {
  905. continue;
  906. }
  907. // For every entity type that requires extra validation
  908. foreach (entity_get_info() as $type => $info) {
  909. $bundles = apachesolr_get_index_bundles($env_id, $type);
  910. // If we're not checking any bundles of this entity type, just skip them all.
  911. if (empty($bundles)) {
  912. continue;
  913. }
  914. if (isset($info['apachesolr']['cron_check'])) {
  915. $callback = $info['apachesolr']['cron_check'];
  916. call_user_func($callback);
  917. }
  918. }
  919. try {
  920. $solr = apachesolr_get_solr($env_id);
  921. if ($optimize_interval && ($time - $last > $optimize_interval)) {
  922. $solr->optimize(FALSE, FALSE);
  923. apachesolr_environment_variable_set($env_id, 'apachesolr_last_optimize', $time);
  924. apachesolr_set_last_index_updated($env_id, $time);
  925. }
  926. // Only clear the cache if the index changed.
  927. // TODO: clear on some schedule if running multi-site.
  928. $updated = apachesolr_get_last_index_updated($env_id);
  929. if ($updated > 0) {
  930. $solr->clearCache();
  931. // Re-populate the luke cache.
  932. $solr->getLuke();
  933. // TODO: an admin interface for setting this. Assume for now 5 minutes.
  934. if ($time - $updated >= variable_get('apachesolr_cache_delay', 300)) {
  935. // Clear the updated flag.
  936. apachesolr_set_last_index_updated($env_id);
  937. }
  938. }
  939. }
  940. catch (Exception $e) {
  941. watchdog('Apache Solr', nl2br(check_plain($e->getMessage())) . ' in apachesolr_cron', NULL, WATCHDOG_ERROR);
  942. }
  943. // We can safely process the apachesolr_cron_limit nodes at a time without a
  944. // timeout or out of memory error.
  945. $limit = variable_get('apachesolr_cron_limit', 50);
  946. apachesolr_index_entities($env_id, $limit);
  947. }
  948. }
  949. /**
  950. * Implements hook_form_[form_id]_alter().
  951. *
  952. * Make sure to flush cache when content types are changed.
  953. */
  954. function apachesolr_form_node_type_form_alter(&$form, $form_state) {
  955. $form['#submit'][] = 'apachesolr_clear_cache';
  956. }
  957. /**
  958. * Implements hook_form_[form_id]_alter(). (D7)
  959. *
  960. * Make sure to flush cache when fields are added.
  961. */
  962. function apachesolr_form_field_ui_field_overview_form_alter(&$form, $form_state) {
  963. $form['#submit'][] = 'apachesolr_clear_cache';
  964. }
  965. /**
  966. * Implements hook_form_[form_id]_alter(). (D7)
  967. *
  968. * Make sure to flush cache when fields are updated.
  969. */
  970. function apachesolr_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  971. $form['#submit'][] = 'apachesolr_clear_cache';
  972. }
  973. /**
  974. * Sets breadcrumb trails for Facet API settings forms.
  975. *
  976. * @param FacetapiAdapter $adapter
  977. * The Facet API adapter object.
  978. * @param array $realm
  979. * The realm definition.
  980. */
  981. function apachesolr_set_facetapi_breadcrumb(FacetapiAdapter $adapter, array $realm) {
  982. if ('apachesolr' == $adapter->getId()) {
  983. // Hack here that depnds on our construction of the searcher name in this way.
  984. list(, $env_id) = explode('@', $adapter->getSearcher());
  985. // Appends additional breadcrumb items.
  986. $breadcrumb = drupal_get_breadcrumb();
  987. $breadcrumb[] = l(t('Apache Solr search environment edit'), 'admin/config/search/apachesolr/settings/' . $env_id);
  988. $breadcrumb[] = l($realm['label'], 'admin/config/search/apachesolr/settings/' . $env_id . '/facets/' . $realm['name']);
  989. drupal_set_breadcrumb($breadcrumb);
  990. }
  991. }
  992. /**
  993. * Implements hook_form_[form_id]_alter(). (D7)
  994. */
  995. function apachesolr_form_facetapi_facet_settings_form_alter(&$form, $form_state) {
  996. apachesolr_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
  997. }
  998. /**
  999. * Implements hook_form_[form_id]_alter(). (D7)
  1000. */
  1001. function apachesolr_form_facetapi_facet_dependencies_form_alter(&$form, $form_state) {
  1002. apachesolr_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
  1003. }
  1004. /**
  1005. * Semaphore that indicates whether a search has been done. Blocks use this
  1006. * later to decide whether they should load or not.
  1007. *
  1008. * @param $searched
  1009. * A boolean indicating whether a search has been executed.
  1010. *
  1011. * @return
  1012. * TRUE if a search has been executed.
  1013. * FALSE otherwise.
  1014. */
  1015. function apachesolr_has_searched($env_id, $searched = NULL) {
  1016. $_searched = &drupal_static(__FUNCTION__, FALSE);
  1017. if (is_bool($searched)) {
  1018. $_searched[$env_id] = $searched;
  1019. }
  1020. // Return false if the search environment is not available in our array
  1021. if (!isset($_searched[$env_id])) {
  1022. return FALSE;
  1023. }
  1024. return $_searched[$env_id];
  1025. }
  1026. /**
  1027. * Semaphore that indicates whether Blocks should be suppressed regardless
  1028. * of whether a search has run.
  1029. *
  1030. * @param $suppress
  1031. * A boolean indicating whether to suppress.
  1032. *
  1033. * @return
  1034. * TRUE if a search has been executed.
  1035. * FALSE otherwise.
  1036. */
  1037. function apachesolr_suppress_blocks($env_id, $suppress = NULL) {
  1038. $_suppress = &drupal_static(__FUNCTION__, FALSE);
  1039. if (is_bool($suppress)) {
  1040. $_suppress[$env_id] = $suppress;
  1041. }
  1042. // Return false if the search environment is not available in our array
  1043. if (!isset($_suppress[$env_id])) {
  1044. return FALSE;
  1045. }
  1046. return $_suppress[$env_id];
  1047. }
  1048. /**
  1049. * Get or set the default environment ID for the current page.
  1050. */
  1051. function apachesolr_default_environment($env_id = NULL) {
  1052. $default_env_id = &drupal_static(__FUNCTION__, NULL);
  1053. if (isset($env_id)) {
  1054. $default_env_id = $env_id;
  1055. }
  1056. if (empty($default_env_id)) {
  1057. $default_env_id = variable_get('apachesolr_default_environment', 'solr');
  1058. }
  1059. return $default_env_id;
  1060. }
  1061. /**
  1062. * Set the default environment and let other modules know about the change.
  1063. */
  1064. function apachesolr_set_default_environment($env_id) {
  1065. $old_env_id = variable_get('apachesolr_default_environment', 'solr');
  1066. variable_set('apachesolr_default_environment', $env_id);
  1067. module_invoke_all('apachesolr_default_environment', $env_id, $old_env_id);
  1068. }
  1069. /**
  1070. * Factory method for solr singleton objects. Structure allows for an arbitrary
  1071. * number of solr objects to be used based on a name whie maps to
  1072. * the host, port, path combination.
  1073. * Get an instance like this:
  1074. * try {
  1075. * $solr = apachesolr_get_solr();
  1076. * }
  1077. * catch (Exception $e) {
  1078. * watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
  1079. * }
  1080. *
  1081. *
  1082. * @param string $env_id
  1083. *
  1084. * @return DrupalApacheSolrServiceInterface $solr
  1085. *
  1086. * @throws Exception
  1087. */
  1088. function apachesolr_get_solr($env_id = NULL) {
  1089. $solr_cache = &drupal_static(__FUNCTION__);
  1090. $environments = apachesolr_load_all_environments();
  1091. if (!interface_exists('DrupalApacheSolrServiceInterface')) {
  1092. require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
  1093. }
  1094. if (empty($env_id)) {
  1095. $env_id = apachesolr_default_environment();
  1096. }
  1097. elseif (empty($environments[$env_id])) {
  1098. throw new Exception(t('Invalid Apache Solr environment: @env_id.', array('@env_id' => $env_id)));
  1099. }
  1100. if (isset($environments[$env_id])) {
  1101. $class = $environments[$env_id]['service_class'];
  1102. if (empty($solr_cache[$env_id])) {
  1103. // Use the default class if none is specified.
  1104. if (empty($class)) {
  1105. $class = variable_get('apachesolr_service_class', 'DrupalApacheSolrService');
  1106. }
  1107. // Takes advantage of auto-loading.
  1108. $solr = new $class($environments[$env_id]['url'], $env_id);
  1109. $soft_commit = apachesolr_environment_variable_get($env_id, 'apachesolr_soft_commit', FALSE);
  1110. if ($soft_commit) {
  1111. $solr->setSoftCommit($soft_commit);
  1112. }
  1113. $solr_cache[$env_id] = $solr;
  1114. }
  1115. return $solr_cache[$env_id];
  1116. }
  1117. else {
  1118. throw new Exception('No default Apache Solr environment.');
  1119. }
  1120. }
  1121. /**
  1122. * Function that loads all the environments
  1123. *
  1124. * @return $environments
  1125. * The environments in the database
  1126. */
  1127. function apachesolr_load_all_environments() {
  1128. $environments = &drupal_static(__FUNCTION__);
  1129. if (isset($environments)) {
  1130. return $environments;
  1131. }
  1132. // Use cache_get to avoid DB when using memcache, etc.
  1133. $cache = cache_get('apachesolr:environments', 'cache_apachesolr');
  1134. if (isset($cache->data)) {
  1135. $environments = $cache->data;
  1136. }
  1137. elseif (!db_table_exists('apachesolr_index_bundles') || !db_table_exists('apachesolr_environment')) {
  1138. // Sometimes this function is called when the 'apachesolr_index_bundles' is
  1139. // not created yet.
  1140. $environments = array();
  1141. }
  1142. else {
  1143. // If ctools is available use its crud functions to load the environments.
  1144. if (module_exists('ctools')) {
  1145. ctools_include('export');
  1146. $environments = ctools_export_load_object('apachesolr_environment', 'all');
  1147. // Convert environments to array.
  1148. foreach ($environments as &$environment) {
  1149. $environment = (array) $environment;
  1150. }
  1151. }
  1152. else {
  1153. $environments = db_query('SELECT * FROM {apachesolr_environment}')->fetchAllAssoc('env_id', PDO::FETCH_ASSOC);
  1154. }
  1155. // Load conf and index bundles. We don't use 'subrecords callback' property
  1156. // of ctools export API.
  1157. apachesolr_environment_load_subrecords($environments);
  1158. cache_set('apachesolr:environments', $environments, 'cache_apachesolr');
  1159. }
  1160. // Allow overrides of environments from settings.php
  1161. $conf_environments = variable_get('apachesolr_environments', array());
  1162. if (!empty($conf_environments)) {
  1163. $environments = drupal_array_merge_deep($environments, $conf_environments);
  1164. }
  1165. return $environments;
  1166. }
  1167. /**
  1168. * Function that loads an environment
  1169. *
  1170. * @param $env_id
  1171. * The environment ID it needs to load.
  1172. *
  1173. * @return $environment
  1174. * The environment that was requested or FALSE if non-existent
  1175. */
  1176. function apachesolr_environment_load($env_id) {
  1177. $environments = apachesolr_load_all_environments();
  1178. return isset($environments[$env_id]) ? $environments[$env_id] : FALSE;
  1179. }
  1180. /**
  1181. * Access callback for the delete page of an environment.
  1182. *
  1183. * @param $permission
  1184. * The permission that you allow access to
  1185. * @param $environment
  1186. * The environment you want to delete. Core environment cannot be deleted
  1187. */
  1188. function apachesolr_environment_delete_page_access($permission, $environment) {
  1189. $is_default = $environment['env_id'] == apachesolr_default_environment();
  1190. return !$is_default && user_access($permission);
  1191. }
  1192. /**
  1193. * Function that deletes an environment
  1194. *
  1195. * @param $env_id
  1196. * The environment ID it needs to delete.
  1197. *
  1198. */
  1199. function apachesolr_environment_delete($env_id) {
  1200. $environment = apachesolr_environment_load($env_id);
  1201. if ($environment) {
  1202. db_delete('apachesolr_environment')
  1203. ->condition('env_id', $env_id)
  1204. ->execute();
  1205. db_delete('apachesolr_environment_variable')
  1206. ->condition('env_id', $env_id)
  1207. ->execute();
  1208. db_delete('apachesolr_index_bundles')
  1209. ->condition('env_id', $env_id)
  1210. ->execute();
  1211. module_invoke_all('apachesolr_environment_delete', $environment);
  1212. apachesolr_environments_clear_cache();
  1213. }
  1214. }
  1215. /**
  1216. * Function that clones an environment
  1217. *
  1218. * @param $env_id
  1219. * The environment ID it needs to clone.
  1220. *
  1221. */
  1222. function apachesolr_environment_clone($env_id) {
  1223. $environment = apachesolr_environment_load($env_id);
  1224. $environments = apachesolr_load_all_environments();
  1225. $environment['env_id'] = apachesolr_create_unique_id($environments, $env_id);
  1226. $environment['name'] = $environment['name'] . ' [cloned]';
  1227. apachesolr_environment_save($environment);
  1228. }
  1229. /**
  1230. * Generator for an unique ID of an environment
  1231. *
  1232. * @param $environments
  1233. * The environments that are available
  1234. * @param $original_environment
  1235. * The environment it needs to replicate an ID for.
  1236. *
  1237. * @return
  1238. * The new environment ID
  1239. */
  1240. function apachesolr_create_unique_id($existing, $id) {
  1241. $count = 0;
  1242. $cloned_env_int = 0;
  1243. do {
  1244. $new_id = $id . '_' . $count;
  1245. $count++;
  1246. } while (isset($existing[$new_id]));
  1247. return $new_id;
  1248. }
  1249. /**
  1250. * Function that saves an environment
  1251. *
  1252. * @param $environment
  1253. * The environment it needs to save.
  1254. *
  1255. */
  1256. function apachesolr_environment_save($environment) {
  1257. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  1258. $default = array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '');
  1259. // If the environment has been saved to the database before, we need to make
  1260. // sure we don't loose anything when saving it; therefore, load the existing
  1261. // environment and merge its' data with the new one.
  1262. $old_environment = apachesolr_environment_load($environment['env_id']);
  1263. if (!empty($old_environment['in_code_only']) && $environment != $old_environment) {
  1264. $environment = drupal_array_merge_deep($old_environment, $environment);
  1265. }
  1266. $conf = isset($environment['conf']) ? $environment['conf'] : array();
  1267. $index_bundles = isset($environment['index_bundles']) ? $environment['index_bundles'] : array();
  1268. // Remove any unexpected fields.
  1269. // @todo - get this from the schema?.
  1270. $environment = array_intersect_key($environment, $default);
  1271. db_merge('apachesolr_environment')
  1272. ->key(array('env_id' => $environment['env_id']))
  1273. ->fields($environment)
  1274. ->execute();
  1275. // Update the environment variables (if any).
  1276. foreach ($conf as $name => $value) {
  1277. db_merge('apachesolr_environment_variable')
  1278. ->key(array('env_id' => $environment['env_id'], 'name' => $name))
  1279. ->fields(array('value' => serialize($value)))
  1280. ->execute();
  1281. }
  1282. // Update the index bundles (if any).
  1283. foreach ($index_bundles as $entity_type => $bundles) {
  1284. apachesolr_index_set_bundles($environment['env_id'], $entity_type, $bundles);
  1285. }
  1286. apachesolr_environments_clear_cache();
  1287. }
  1288. /**
  1289. * Clear all caches for environments.
  1290. */
  1291. function apachesolr_environments_clear_cache() {
  1292. cache_clear_all('apachesolr:environments', 'cache_apachesolr');
  1293. drupal_static_reset('apachesolr_load_all_environments');
  1294. drupal_static_reset('apachesolr_get_solr');
  1295. if (module_exists('ctools')) {
  1296. ctools_include('export');
  1297. ctools_export_load_object_reset('apachesolr_environment');
  1298. }
  1299. }
  1300. /**
  1301. * Get a named variable, or return the default.
  1302. *
  1303. * @see variable_get()
  1304. */
  1305. function apachesolr_environment_variable_get($env_id, $name, $default = NULL) {
  1306. $environment = apachesolr_environment_load($env_id);
  1307. if (isset($environment['conf'][$name])) {
  1308. return $environment['conf'][$name];
  1309. }
  1310. return $default;
  1311. }
  1312. /**
  1313. * Set a named variable, or return the default.
  1314. *
  1315. * @see variable_set()
  1316. */
  1317. function apachesolr_environment_variable_set($env_id, $name, $value) {
  1318. apachesolr_environment_save_to_database($env_id);
  1319. db_merge('apachesolr_environment_variable')
  1320. ->key(array('env_id' => $env_id, 'name' => $name))
  1321. ->fields(array('value' => serialize($value)))
  1322. ->execute();
  1323. apachesolr_environments_clear_cache();
  1324. }
  1325. /**
  1326. * Get a named variable, or return the default.
  1327. *
  1328. * @see variable_del()
  1329. */
  1330. function apachesolr_environment_variable_del($env_id, $name) {
  1331. apachesolr_environment_save_to_database($env_id);
  1332. db_delete('apachesolr_environment_variable')
  1333. ->condition('env_id', $env_id)
  1334. ->condition('name', $name)
  1335. ->execute();
  1336. apachesolr_environments_clear_cache();
  1337. }
  1338. /**
  1339. * Makes sure that the given environment has been saved to the database.
  1340. *
  1341. * This is a required step before any environment-related data is modified or
  1342. * deleted. It ensures that ctools exportables can properly determine whether
  1343. * something has been overridden.
  1344. *
  1345. * @param string $env_id
  1346. * The environment ID.
  1347. *
  1348. * @see https://www.drupal.org/node/1439564#comment-8727467
  1349. */
  1350. function apachesolr_environment_save_to_database($env_id) {
  1351. $environment = apachesolr_environment_load($env_id);
  1352. if (!empty($environment['in_code_only'])) {
  1353. apachesolr_environment_save($environment);
  1354. }
  1355. }
  1356. /**
  1357. * Checks if a specific Apache Solr server is available.
  1358. *
  1359. * @return boolean TRUE if the server can be pinged, FALSE otherwise.
  1360. */
  1361. function apachesolr_server_status($url, $class = NULL) {
  1362. $status = &drupal_static(__FUNCTION__, array());
  1363. if (!interface_exists('DrupalApacheSolrServiceInterface')) {
  1364. require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
  1365. }
  1366. if (empty($class)) {
  1367. $class = variable_get('apachesolr_service_class', 'DrupalApacheSolrService');
  1368. }
  1369. $key = $url . '|' . $class;
  1370. // Static store insures we don't ping the server more than once per page load.
  1371. if (!isset($status[$key])) {
  1372. $ping = FALSE;
  1373. try {
  1374. // Takes advantage of auto-loading.
  1375. // @Todo : Do we have to specify the env_id?
  1376. $solr = new $class($url);
  1377. $ping = @$solr->ping(variable_get('apachesolr_ping_timeout', 4));
  1378. }
  1379. catch (Exception $e) {
  1380. watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
  1381. }
  1382. $status[$key] = $ping;
  1383. }
  1384. return $status[$key];
  1385. }
  1386. /**
  1387. * Execute a keyword search based on a query object.
  1388. *
  1389. * Normally this function is used with the default (dismax) handler for keyword
  1390. * searches. The $final_query that's returned will have been modified by
  1391. * both hook_apachesolr_query_prepare() and hook_apachesolr_query_alter().
  1392. *
  1393. * @param $current_query
  1394. * A query object from apachesolr_drupal_query(). It will be modified by
  1395. * hook_apachesolr_query_prepare() and then cached in apachesolr_current_query().
  1396. * @param $page
  1397. * For paging into results, using $current_query->params['rows'] results per page.
  1398. *
  1399. * @return array($final_query, $response)
  1400. *
  1401. * @throws Exception
  1402. */
  1403. function apachesolr_do_query(DrupalSolrQueryInterface $current_query) {
  1404. if (!is_object($current_query)) {
  1405. throw new Exception(t('NULL query object in function apachesolr_do_query()'));
  1406. }
  1407. // Allow modules to alter the query prior to statically caching it.
  1408. // This can e.g. be used to add available sorts.
  1409. $searcher = $current_query->getSearcher();
  1410. if (module_exists('facetapi')) {
  1411. // Gets enabled facets, adds filter queries to $params.
  1412. $adapter = facetapi_adapter_load($searcher);
  1413. if ($adapter) {
  1414. // Realm could be added but we want all the facets
  1415. $adapter->addActiveFilters($current_query);
  1416. }
  1417. }
  1418. foreach (module_implements('apachesolr_query_prepare') as $module) {
  1419. $function_name = $module . '_apachesolr_query_prepare';
  1420. $function_name($current_query);
  1421. }
  1422. // Cache the original query. Since all the built queries go through
  1423. // this process, all the hook_invocations will happen later
  1424. $env_id = $current_query->solr('getId');
  1425. // Add our defType setting here. Normally this would be dismax or the setting
  1426. // from the solrconfig.xml. This allows the setting to be overridden.
  1427. $defType = apachesolr_environment_variable_get($env_id, 'apachesolr_query_type');
  1428. if (!empty($defType)) {
  1429. $current_query->addParam('defType', $defType);
  1430. }
  1431. $query = apachesolr_current_query($env_id, $current_query);
  1432. // Verify if this query was already executed in the same page load
  1433. if ($response = apachesolr_static_response_cache($searcher)) {
  1434. // Return cached query object
  1435. return array($query, $response);
  1436. }
  1437. $query->addParam('start', $query->page * $query->getParam('rows'));
  1438. // This hook allows modules to modify the query and params objects.
  1439. drupal_alter('apachesolr_query', $query);
  1440. if ($query->abort_search) {
  1441. // A module implementing HOOK_apachesolr_query_alter() aborted the search.
  1442. return array(NULL, array());
  1443. }
  1444. $keys = $query->getParam('q');
  1445. if (strlen($keys) == 0 && ($filters = $query->getFilters())) {
  1446. // Move the fq params to q.alt for better performance. Only suitable
  1447. // when using dismax or edismax, so we keep this out of the query class itself
  1448. // for now.
  1449. $qalt = array();
  1450. foreach ($filters as $delta => $filter) {
  1451. // Move the fq param if it has no local params and is not negative.
  1452. if (!$filter['#exclude'] && !$filter['#local']) {
  1453. $qalt[] = '(' . $query->makeFilterQuery($filter) . ')';
  1454. $query->removeFilter($filter['#name'], $filter['#value'], $filter['#exclude']);
  1455. }
  1456. }
  1457. if ($qalt) {
  1458. $query->addParam('q.alt', implode(' ', $qalt));
  1459. }
  1460. }
  1461. // We must run htmlspecialchars() here since converted entities are in the index.
  1462. // and thus bare entities &, > or < won't match. Single quotes are converted
  1463. // too, but not double quotes since the dismax parser looks at them for
  1464. // phrase queries.
  1465. $keys = htmlspecialchars($keys, ENT_NOQUOTES, 'UTF-8');
  1466. $keys = str_replace("'", '&#039;', $keys);
  1467. $response = $query->search($keys);
  1468. // The response is cached so that it is accessible to the blocks and anything
  1469. // else that needs it beyond the initial search.
  1470. apachesolr_static_response_cache($searcher, $response);
  1471. return array($query, $response);
  1472. }
  1473. /**
  1474. * It is important to hold on to the Solr response object for the duration of the
  1475. * page request so that we can use it for things like building facet blocks.
  1476. *
  1477. * @param $searcher
  1478. * Name of the searcher - e.g. from $query->getSearcher().
  1479. */
  1480. function apachesolr_static_response_cache($searcher, $response = NULL) {
  1481. $_response = &drupal_static(__FUNCTION__, array());
  1482. if (is_object($response)) {
  1483. $_response[$searcher] = clone $response;
  1484. }
  1485. if (!isset($_response[$searcher])) {
  1486. $_response[$searcher] = NULL;
  1487. }
  1488. return $_response[$searcher];
  1489. }
  1490. /**
  1491. * Factory function for query objects.
  1492. *
  1493. * @param string $name
  1494. * The search name, used for finding the correct blocks and other config.
  1495. * Typically "apachesolr".
  1496. * @param array $params
  1497. * Array of params , such as 'q', 'fq' to be applied.
  1498. * @param string $solrsort
  1499. * Visible string telling solr how to sort.
  1500. * @param string $base_path
  1501. * The search base path (without the keywords) for this query.
  1502. * @param DrupalApacheSolrServiceInterface $solr
  1503. * An instance of DrupalApacheSolrServiceInterface.
  1504. *
  1505. * @return DrupalSolrQueryInterface
  1506. * DrupalSolrQueryInterface object.
  1507. *
  1508. * @throws Exception
  1509. */
  1510. function apachesolr_drupal_query($name, array $params = array(), $solrsort = '', $base_path = '', DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) {
  1511. if (!interface_exists('DrupalSolrQueryInterface')) {
  1512. require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
  1513. }
  1514. $class_info = variable_get('apachesolr_query_class', array(
  1515. 'file' => 'Solr_Base_Query',
  1516. 'module' => 'apachesolr',
  1517. 'class' => 'SolrBaseQuery'));
  1518. $class = $class_info['class'];
  1519. if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) {
  1520. module_load_include('php', $class_info['module'], $class_info['file']);
  1521. }
  1522. if (empty($solr)) {
  1523. $solr = apachesolr_get_solr();
  1524. }
  1525. return new $class($name, $solr, $params, $solrsort, $base_path, $context);
  1526. }
  1527. /**
  1528. * Factory function for query objects.
  1529. *
  1530. * @param $operator
  1531. * Whether the subquery should be added to another query as OR or AND
  1532. *
  1533. * @return DrupalSolrQueryInterface|false
  1534. * Subquery or error.
  1535. *
  1536. * @throws Exception
  1537. */
  1538. function apachesolr_drupal_subquery($operator = 'OR') {
  1539. if (!interface_exists('DrupalSolrQueryInterface')) {
  1540. require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
  1541. }
  1542. $class_info = variable_get('apachesolr_subquery_class', array(
  1543. 'file' => 'Solr_Base_Query',
  1544. 'module' => 'apachesolr',
  1545. 'class' => 'SolrFilterSubQuery'));
  1546. $class = $class_info['class'];
  1547. if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) {
  1548. module_load_include('php', $class_info['module'], $class_info['file']);
  1549. }
  1550. $query = new $class($operator);
  1551. return $query;
  1552. }
  1553. /**
  1554. * Static getter/setter for the current query. Only set once per page.
  1555. *
  1556. * @param $env_id
  1557. * Environment from which to save or get the current query
  1558. * @param DrupalSolrQueryInterface $query
  1559. * $query object to save in the static
  1560. *
  1561. * @return DrupalSolrQueryInterface|null
  1562. * return the $query object if it is available in the drupal_static or null otherwise
  1563. */
  1564. function apachesolr_current_query($env_id, DrupalSolrQueryInterface $query = NULL) {
  1565. $saved_query = &drupal_static(__FUNCTION__, NULL);
  1566. if (is_object($query)) {
  1567. $saved_query[$env_id] = clone $query;
  1568. }
  1569. if (empty($saved_query[$env_id])) {
  1570. return NULL;
  1571. }
  1572. return is_object($saved_query[$env_id]) ? clone $saved_query[$env_id] : NULL;
  1573. }
  1574. /**
  1575. *
  1576. */
  1577. /**
  1578. * Construct a dynamic index name based on information about a field.
  1579. *
  1580. * @param array $field
  1581. * array(
  1582. * 'index_type' => 'integer',
  1583. * 'multiple' => TRUE,
  1584. * 'name' => 'fieldname',
  1585. * ),
  1586. * @return string
  1587. * Fieldname as it appears in the solr index
  1588. */
  1589. function apachesolr_index_key($field) {
  1590. $index_type = !empty($field['index_type']) ? $field['index_type'] : NULL;
  1591. switch ($index_type) {
  1592. case 'text':
  1593. $type_prefix = 't';
  1594. break;
  1595. case 'text-omitNorms':
  1596. $type_prefix = 'to';
  1597. break;
  1598. case 'text-unstemmed':
  1599. $type_prefix = 'tu';
  1600. break;
  1601. case 'text-edgeNgram':
  1602. $type_prefix = 'te';
  1603. break;
  1604. case 'text-whiteSpace':
  1605. $type_prefix = 'tw';
  1606. break;
  1607. case 'integer':
  1608. $type_prefix = 'i'; // long integer
  1609. break;
  1610. case 'half-int':
  1611. $type_prefix = 'h'; // 32 bit integer
  1612. break;
  1613. case 'float':
  1614. $type_prefix = 'f'; // float; sortable.
  1615. break;
  1616. case 'double':
  1617. $type_prefix = 'p'; // double; sortable d was used for date.
  1618. break;
  1619. case 'boolean':
  1620. $type_prefix = 'b';
  1621. break;
  1622. case 'tint':
  1623. $type_prefix = 'it'; // long integer trie; sortable, best for range queries
  1624. break;
  1625. case 'thalf-int':
  1626. $type_prefix = 'ht'; // 32 bit integer trie (sortable)
  1627. break;
  1628. case 'tfloat':
  1629. $type_prefix = 'ft'; // float trie; sortable, best for range queries.
  1630. break;
  1631. case 'tdouble':
  1632. $type_prefix = 'pt'; // double trie;
  1633. break;
  1634. case 'sint':
  1635. $type_prefix = 'is'; // long integer sortable (deprecated)
  1636. break;
  1637. case 'half-sint':
  1638. $type_prefix = 'hs'; // 32 bit integer long sortable (deprecated)
  1639. break;
  1640. case 'sfloat':
  1641. $type_prefix = 'fs'; // float, sortable (use for sorting missing last) (deprecated).
  1642. break;
  1643. case 'sdouble':
  1644. $type_prefix = 'ps'; // double sortable; (use for sorting missing last) (deprecated).
  1645. break;
  1646. case 'date':
  1647. $type_prefix = 'd'; // date trie (sortable)
  1648. break;
  1649. case 'date-deprecated':
  1650. $type_prefix = 'dd'; // date (regular)
  1651. break;
  1652. case 'binary':
  1653. $type_prefix = 'x'; // Anything that is base64 encoded
  1654. break;
  1655. case 'storage':
  1656. $type_prefix = 'z'; // Anything that just need to be stored, not indexed
  1657. break;
  1658. case 'point':
  1659. $type_prefix = 'point'; // PointType. "52.3672174,4.9126891"
  1660. break;
  1661. case 'location':
  1662. $type_prefix = 'loc'; // LatLonType. "52.3672174,4.9126891"
  1663. break;
  1664. case 'geohash':
  1665. $type_prefix = 'geo'; // GeohashField. "42.6" http://en.wikipedia.org/wiki/Geohash
  1666. break;
  1667. case 'string':
  1668. default:
  1669. $type_prefix = 's'; // String
  1670. }
  1671. $sm = !empty($field['multiple']) ? 'm_' : 's_';
  1672. // Legacy: Block deltas are limited to 32 chars. Keep it like this for backwards compatibility
  1673. $apachesolr_field_length_limit = variable_get('apachesolr_field_length_limit', '32');
  1674. if ($apachesolr_field_length_limit <= 0) {
  1675. return $type_prefix . $sm . $field['name'];
  1676. }
  1677. else {
  1678. return substr($type_prefix . $sm . $field['name'], 0, $apachesolr_field_length_limit);
  1679. }
  1680. }
  1681. /**
  1682. * Try to map a schema field name to a human-readable description.
  1683. */
  1684. function apachesolr_field_name_map($field_name) {
  1685. $map = &drupal_static(__FUNCTION__);
  1686. if (!isset($map)) {
  1687. $map = array(
  1688. 'content' => t('The full, rendered content (e.g. the rendered node body)'),
  1689. 'ts_comments' => t('The rendered comments associated with a node'),
  1690. 'tos_content_extra' => t('Extra rendered content or keywords'),
  1691. 'tos_name_formatted' => t('Author name (Formatted)'),
  1692. 'label' => t('Title or label'),
  1693. 'teaser' => t('Teaser or preview'),
  1694. 'tos_name' => t('Author name'),
  1695. 'path_alias' => t('Path alias'),
  1696. 'taxonomy_names' => t('All taxonomy term names'),
  1697. 'tags_h1' => t('Body text inside H1 tags'),
  1698. 'tags_h2_h3' => t('Body text inside H2 or H3 tags'),
  1699. 'tags_h4_h5_h6' => t('Body text inside H4, H5, or H6 tags'),
  1700. 'tags_inline' => t('Body text in inline tags like EM or STRONG'),
  1701. 'tags_a' => t('Body text inside links (A tags)'),
  1702. 'tid' => t('Taxonomy term IDs'),
  1703. 'is_uid' => t('User IDs'),
  1704. 'bundle' => t('Content type names eg. article'),
  1705. 'entity_type' => t('Entity type names eg. node'),
  1706. 'ss_language' => t('Language type eg. en or und (undefinded)'),
  1707. );
  1708. if (module_exists('taxonomy')) {
  1709. foreach (taxonomy_get_vocabularies() as $vocab) {
  1710. $map['tm_vid_' . $vocab->vid . '_names'] = t('Taxonomy term names only from the %name vocabulary', array('%name' => $vocab->name));
  1711. $map['im_vid_' . $vocab->vid] = t('Taxonomy term IDs from the %name vocabulary', array('%name' => $vocab->name));
  1712. }
  1713. }
  1714. foreach (apachesolr_entity_fields('node') as $field_nm => $nodefields) {
  1715. foreach ($nodefields as $field_info) {
  1716. $map[apachesolr_index_key($field_info)] = t('Field of type @type: %label', array('@type' => $field_info['field']['type'], '%label' => $field_info['display_name']));
  1717. }
  1718. }
  1719. drupal_alter('apachesolr_field_name_map', $map);
  1720. }
  1721. return isset($map[$field_name]) ? $map[$field_name] : $field_name;
  1722. }
  1723. /**
  1724. * Validation function for the Facet API facet settings form.
  1725. *
  1726. * Apache Solr does not support the combination of OR facets
  1727. * and facet missing, so catch that at validation.
  1728. */
  1729. function apachesolr_facet_form_validate($form, &$form_state) {
  1730. if (($form_state['values']['global']['operator'] == FACETAPI_OPERATOR_OR) && $form_state['values']['global']['facet_missing']) {
  1731. form_set_error('operator', t('Apache Solr does not support <em>facet missing</em> in combination with the OR operator.'));
  1732. }
  1733. }
  1734. /**
  1735. * Implements hook_module_implements_alter().
  1736. */
  1737. function apachesolr_module_implements_alter(&$implementations, $hook) {
  1738. // This module's hook_entity_info_alter() implementation should run last
  1739. // since it needs to examine the list of bundles for each entity type, which
  1740. // may have been changed in earlier hook_entity_info_alter() implementations
  1741. // (for example, the File Entity module does this for file entities).
  1742. if ($hook == 'entity_info_alter') {
  1743. $group = $implementations['apachesolr'];
  1744. unset($implementations['apachesolr']);
  1745. $implementations['apachesolr'] = $group;
  1746. }
  1747. }
  1748. /**
  1749. * Implements hook_entity_info_alter().
  1750. */
  1751. function apachesolr_entity_info_alter(&$entity_info) {
  1752. // Load all environments
  1753. $environments = apachesolr_load_all_environments();
  1754. // Set those values that we know. Other modules can do so
  1755. // for their own entities if they want.
  1756. $default_entity_info = array();
  1757. $default_entity_info['node']['indexable'] = TRUE;
  1758. $default_entity_info['node']['status callback'][] = 'apachesolr_index_node_status_callback';
  1759. $default_entity_info['node']['document callback'][] = 'apachesolr_index_node_solr_document';
  1760. $default_entity_info['node']['reindex callback'] = 'apachesolr_index_node_solr_reindex';
  1761. $default_entity_info['node']['bundles changed callback'] = 'apachesolr_index_node_bundles_changed';
  1762. $default_entity_info['node']['index_table'] = 'apachesolr_index_entities_node';
  1763. $default_entity_info['node']['cron_check'] = 'apachesolr_index_node_check_table';
  1764. // apachesolr_search implements a new callback for every entity type
  1765. // $default_entity_info['node']['result callback'] = 'apachesolr_search_node_result';
  1766. //Allow implementations of HOOK_apachesolr_entity_info to modify these default indexers
  1767. drupal_alter('apachesolr_entity_info', $default_entity_info);
  1768. // First set defaults so that we don't need to worry about NULL keys.
  1769. foreach (array_keys($entity_info) as $type) {
  1770. if (!isset($entity_info[$type]['apachesolr'])) {
  1771. $entity_info[$type]['apachesolr'] = array();
  1772. }
  1773. if (isset($default_entity_info[$type])) {
  1774. $entity_info[$type]['apachesolr'] += $default_entity_info[$type];
  1775. }
  1776. $default = array(
  1777. 'indexable' => FALSE,
  1778. 'status callback' => '',
  1779. 'document callback' => '',
  1780. 'reindex callback' => '',
  1781. 'bundles changed callback' => '',
  1782. );
  1783. $entity_info[$type]['apachesolr'] += $default;
  1784. }
  1785. // For any supported entity type and bundle, flag it for indexing.
  1786. foreach ($entity_info as $entity_type => $info) {
  1787. if ($info['apachesolr']['indexable']) {
  1788. // Loop over each environment and check if any of them have other entity
  1789. // bundles of any entity type enabled and set the index value to TRUE
  1790. foreach ($environments as $env) {
  1791. // Skip if the environment is set to read only
  1792. if (empty($env['env_id']['conf']['apachesolr_read_only'])) {
  1793. // Get the supported bundles
  1794. $supported = apachesolr_get_index_bundles($env['env_id'], $entity_type);
  1795. // For each bundle in drupal, compare to the supported apachesolr
  1796. // bundles and enable where possible
  1797. foreach (array_keys($info['bundles']) as $bundle) {
  1798. if (in_array($bundle, $supported)) {
  1799. $entity_info[$entity_type]['bundles'][$bundle]['apachesolr']['index'] = TRUE;
  1800. }
  1801. }
  1802. }
  1803. }
  1804. }
  1805. }
  1806. }
  1807. /**
  1808. * Gets a list of the bundles on the specified entity type that should be indexed.
  1809. *
  1810. * @param string $core
  1811. * The Solr environment for which to index entities.
  1812. * @param string $entity_type
  1813. * The entity type to index.
  1814. * @return array
  1815. * The bundles that should be indexed.
  1816. */
  1817. function apachesolr_get_index_bundles($env_id, $entity_type) {
  1818. $environment = apachesolr_environment_load($env_id);
  1819. return !empty($environment['index_bundles'][$entity_type]) ? $environment['index_bundles'][$entity_type] : array();
  1820. }
  1821. /**
  1822. * Implements hook_entity_insert().
  1823. */
  1824. function apachesolr_entity_insert($entity, $type) {
  1825. // For our purposes there's really no difference between insert and update.
  1826. return apachesolr_entity_update($entity, $type);
  1827. }
  1828. /**
  1829. * Determines if we should index the provided entity.
  1830. *
  1831. * Whether or not a given entity is indexed is determined on a per-bundle basis.
  1832. * Entities/Bundles that have no index flag are presumed to not get indexed.
  1833. *
  1834. * @param stdClass $entity
  1835. * The entity we may or may not want to index.
  1836. * @param string $type
  1837. * The type of entity.
  1838. * @return boolean
  1839. * TRUE if this entity should be indexed, FALSE otherwise.
  1840. */
  1841. function apachesolr_entity_should_index($entity, $type) {
  1842. $info = entity_get_info($type);
  1843. list($id, $vid, $bundle) = entity_extract_ids($type, $entity);
  1844. if ($bundle && isset($info['bundles'][$bundle]['apachesolr']['index']) && $info['bundles'][$bundle]['apachesolr']['index']) {
  1845. return TRUE;
  1846. }
  1847. return FALSE;
  1848. }
  1849. /**
  1850. * Implements hook_entity_update().
  1851. */
  1852. function apachesolr_entity_update($entity, $type) {
  1853. // Include the index file for the status callback
  1854. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  1855. if (apachesolr_entity_should_index($entity, $type)) {
  1856. $info = entity_get_info($type);
  1857. list($id, $vid, $bundle) = entity_extract_ids($type, $entity);
  1858. // Check status callback before sending to the index
  1859. $status_callbacks = apachesolr_entity_get_callback($type, 'status callback', $bundle);
  1860. $status = TRUE;
  1861. if (is_array($status_callbacks)) {
  1862. foreach($status_callbacks as $status_callback) {
  1863. if (is_callable($status_callback)) {
  1864. // By placing $status in front we prevent calling any other callback
  1865. // after one status callback returned false.
  1866. // The entity being saved is passed to the status callback in
  1867. // addition to $id in case the callback needs to examine properties
  1868. // such as the current node revision which cannot be determined by
  1869. // loading a fresh copy of the entity.
  1870. $status = $status && $status_callback($id, $type, $entity);
  1871. }
  1872. }
  1873. }
  1874. // Delete the entity from our index if the status callback returns FALSE
  1875. if (!$status) {
  1876. apachesolr_entity_delete($entity, $type);
  1877. return NULL;
  1878. }
  1879. $indexer_table = apachesolr_get_indexer_table($type);
  1880. // If we haven't seen this entity before it may not be there, so merge
  1881. // instead of update.
  1882. db_merge($indexer_table)
  1883. ->key(array(
  1884. 'entity_type' => $type,
  1885. 'entity_id' => $id,
  1886. ))
  1887. ->fields(array(
  1888. 'bundle' => $bundle,
  1889. 'status' => 1,
  1890. 'changed' => REQUEST_TIME,
  1891. ))
  1892. ->execute();
  1893. }
  1894. }
  1895. /**
  1896. * Retrieve the indexer table for an entity type.
  1897. */
  1898. function apachesolr_get_indexer_table($type) {
  1899. $entity_info = entity_get_info();
  1900. if (isset($entity_info[$type]['apachesolr']['index_table'])) {
  1901. $indexer_table = $entity_info[$type]['apachesolr']['index_table'];
  1902. }
  1903. else {
  1904. $indexer_table = 'apachesolr_index_entities';
  1905. }
  1906. return $indexer_table;
  1907. }
  1908. /**
  1909. * Implements hook_entity_delete().
  1910. *
  1911. * Delete the entity's entry from a fictional table of all entities.
  1912. */
  1913. function apachesolr_entity_delete($entity, $entity_type) {
  1914. list($entity_id) = entity_extract_ids($entity_type, $entity);
  1915. // Get all environments and delete it from their table and index
  1916. $environments = apachesolr_load_all_environments();
  1917. foreach ($environments as $environment) {
  1918. apachesolr_remove_entity($environment['env_id'], $entity_type, $entity_id);
  1919. }
  1920. }
  1921. /**
  1922. * Remove a specific entity from a given Solr environment.
  1923. *
  1924. * @param string $env_id
  1925. * @param string $entity_type
  1926. * @param string $entity_id
  1927. */
  1928. function apachesolr_remove_entity($env_id, $entity_type, $entity_id) {
  1929. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  1930. $indexer_table = apachesolr_get_indexer_table($entity_type);
  1931. if (apachesolr_index_delete_entity_from_index($env_id, $entity_type, $entity_id)) {
  1932. // There was no exception, so delete from the table.
  1933. db_delete($indexer_table)
  1934. ->condition('entity_type', $entity_type)
  1935. ->condition('entity_id', $entity_id)
  1936. ->execute();
  1937. }
  1938. else {
  1939. // Set status 0 so we try to delete from the index again in the future.
  1940. db_update($indexer_table)
  1941. ->condition('entity_id', $entity_id)
  1942. ->fields(array('changed' => REQUEST_TIME, 'status' => 0))
  1943. ->execute();
  1944. }
  1945. }
  1946. /**
  1947. * Returns array containing information about node fields that should be indexed
  1948. */
  1949. function apachesolr_entity_fields($entity_type = 'node') {
  1950. $fields = &drupal_static(__FUNCTION__, array());
  1951. if (!isset($fields[$entity_type])) {
  1952. $fields[$entity_type] = array();
  1953. // Get the field mappings from apachesolr_field_mappings() implementations.
  1954. $mappings = apachesolr_get_field_mappings($entity_type);
  1955. $modules = system_get_info('module');
  1956. $instances = field_info_instances($entity_type);
  1957. foreach (field_info_fields() as $field_name => $field) {
  1958. $row = array();
  1959. if (isset($field['bundles'][$entity_type]) && (isset($mappings['per-field'][$field_name]) || isset($mappings[$field['type']]))) {
  1960. // Find the mapping.
  1961. if (isset($mappings['per-field'][$field_name])) {
  1962. $row = $mappings['per-field'][$field_name];
  1963. }
  1964. else {
  1965. $row = $mappings[$field['type']];
  1966. }
  1967. // The field info array.
  1968. $row['field'] = $field;
  1969. // Cardinality: The number of values the field can hold. Legal values
  1970. // are any positive integer or FIELD_CARDINALITY_UNLIMITED.
  1971. if ($row['field']['cardinality'] != 1) {
  1972. $row['multiple'] = TRUE;
  1973. }
  1974. // @todo: for fields like taxonomy we are indexing multiple Solr fields
  1975. // per entity field, but are keying on a single Solr field name here.
  1976. $function = !empty($row['name callback']) ? $row['name callback'] : NULL;
  1977. if ($function && is_callable($function)) {
  1978. $row['name'] = $function($field);
  1979. }
  1980. else {
  1981. $row['name'] = $field['field_name'];
  1982. }
  1983. $row['module_name'] = $modules[$field['module']]['name'];
  1984. // Set display name
  1985. $display_name = array();
  1986. foreach ($field['bundles'][$entity_type] as $bundle) {
  1987. $field_display = isset($instances[$bundle][$field_name]['display']) ? $instances[$bundle][$field_name]['display'] : array();
  1988. if (empty($field_display['search_index']) || (isset($field_display['search_index']['type']) && $field_display['search_index']['type'] != 'hidden')) {
  1989. $row['display_name'] = $instances[$bundle][$field_name]['label'];
  1990. $row['bundles'][] = $bundle;
  1991. }
  1992. }
  1993. // Only add to the $fields array if some instances are displayed for the search index.
  1994. if (!empty($row['bundles'])) {
  1995. // Use the Solr index key as the array key.
  1996. $fields[$entity_type][apachesolr_index_key($row)][] = $row;
  1997. }
  1998. }
  1999. }
  2000. }
  2001. return $fields[$entity_type];
  2002. }
  2003. /**
  2004. * Gets the Apache Solr field mappings.
  2005. *
  2006. * Field mappings define the various callbacks and Facet API keys associated
  2007. * with field types, i.e. "integer", "date", etc. Mappings are gathered by
  2008. * invoking hook_apachesolr_field_mappings().
  2009. *
  2010. * @param string $entity_type
  2011. * The machine name of the entity mappings are being collected for.
  2012. *
  2013. * @return array
  2014. * An associative array keyed by field type to an array of mappings
  2015. * containing:
  2016. * - dependency plugins: The Facet API dependency plugins associated with
  2017. * fields of this type.
  2018. * - map callback: The Facet API map callback that converts the raw values
  2019. * stored in the index to something human readable.
  2020. * - name callback: Callback used to modify the base name of the field as it
  2021. * is stored in Solr. For example, the name callback cound change an integer
  2022. * field from "field_foo" to "field_bar" so that it is stored in Solr as
  2023. * "i_field_bar".
  2024. * - hierarchy callback: The Facet API hierarchy processing callback for
  2025. * hierarchical facets.
  2026. * - indexing_callback: Callback used to retrieve values for indexing.
  2027. * - index_type: The Solr datatype associated with this field type.
  2028. * - facets: A boolean flagging whether facets are allowed for this field.
  2029. * - facet missing allowed: A boolean flagging whether the Facet API "missing
  2030. * facets" setting is supported by fields of this type.
  2031. * - facet mincount allowed: A boolean flagging whether the Facet API "minimum
  2032. * facet count" setting is supported by fields of this type.
  2033. * - multiple: A boolean flagging whether the field contains multiple values.
  2034. *
  2035. * @see http://drupal.org/node/1825426
  2036. */
  2037. function apachesolr_get_field_mappings($entity_type) {
  2038. $field_mappings = &drupal_static(__FUNCTION__, array());
  2039. if (!isset($field_mappings[$entity_type])) {
  2040. $field_mappings[$entity_type] = module_invoke_all('apachesolr_field_mappings');
  2041. $mappings = &$field_mappings[$entity_type];
  2042. foreach (array_keys($mappings) as $key) {
  2043. // Set all values with defaults.
  2044. $defaults = array(
  2045. 'dependency plugins' => array('bundle', 'role'),
  2046. 'map callback' => FALSE,
  2047. 'name callback' => '',
  2048. 'hierarchy callback' => FALSE,
  2049. 'indexing_callback' => '',
  2050. 'index_type' => 'string',
  2051. 'facets' => FALSE,
  2052. 'facet missing allowed' => FALSE,
  2053. 'facet mincount allowed' => FALSE,
  2054. // Field API allows any field to be multi-valued.
  2055. 'multiple' => TRUE,
  2056. );
  2057. if ($key !== 'per-field') {
  2058. $mappings[$key] += $defaults;
  2059. }
  2060. else {
  2061. foreach (array_keys($field_mappings[$entity_type][$key]) as $field_key) {
  2062. $mappings[$key][$field_key] += $defaults;
  2063. }
  2064. }
  2065. }
  2066. // Allow other modules to add or alter the field mappings.
  2067. drupal_alter('apachesolr_field_mappings', $mappings, $entity_type);
  2068. }
  2069. return $field_mappings[$entity_type];
  2070. }
  2071. /**
  2072. * Implements hook_apachesolr_index_document_build().
  2073. */
  2074. function field_apachesolr_index_document_build(ApacheSolrDocument $document, $entity, $entity_type) {
  2075. $info = entity_get_info($entity_type);
  2076. if ($info['fieldable']) {
  2077. // Handle fields including taxonomy.
  2078. $indexed_fields = apachesolr_entity_fields($entity_type);
  2079. foreach ($indexed_fields as $index_key => $nodefields) {
  2080. foreach ($nodefields as $field_info) {
  2081. $field_name = $field_info['field']['field_name'];
  2082. // See if the node has fields that can be indexed
  2083. if (isset($entity->{$field_name})) {
  2084. // Got a field.
  2085. $functions = $field_info['indexing_callback'];
  2086. if (!is_array($functions)) {
  2087. $functions = array($functions);
  2088. }
  2089. foreach ($functions as $function) {
  2090. if ($function && function_exists($function)) {
  2091. // NOTE: This function should always return an array. One
  2092. // entity field may be indexed to multiple Solr fields.
  2093. $fields = $function($entity, $field_name, $index_key, $field_info);
  2094. foreach ($fields as $field) {
  2095. // It's fine to use this method also for single value fields.
  2096. $document->setMultiValue($field['key'], $field['value']);
  2097. }
  2098. }
  2099. }
  2100. }
  2101. }
  2102. }
  2103. }
  2104. }
  2105. /**
  2106. * Implements hook_apachesolr_index_document_build_node().
  2107. *
  2108. * Adds book module support
  2109. */
  2110. function apachesolr_apachesolr_index_document_build_node(ApacheSolrDocument $document, $entity, $env_id) {
  2111. // Index book module data.
  2112. if (!empty($entity->book['bid'])) {
  2113. // Hard-coded - must change if apachesolr_index_key() changes.
  2114. $document->is_book_bid = (int) $entity->book['bid'];
  2115. }
  2116. }
  2117. /**
  2118. * Strip html tags and also control characters that cause Jetty/Solr to fail.
  2119. */
  2120. function apachesolr_clean_text($text) {
  2121. // Remove invisible content.
  2122. $text = preg_replace('@<(applet|audio|canvas|command|embed|iframe|map|menu|noembed|noframes|noscript|script|style|svg|video)[^>]*>.*</\1>@siU', ' ', $text);
  2123. // Add spaces before stripping tags to avoid running words together.
  2124. $text = filter_xss(str_replace(array('<', '>'), array(' <', '> '), $text), array());
  2125. // Decode entities and then make safe any < or > characters.
  2126. $text = htmlspecialchars(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8');
  2127. // Remove extra spaces.
  2128. $text = preg_replace('/\s+/s', ' ', $text);
  2129. // Remove white spaces around punctuation marks probably added
  2130. // by the safety operations above. This is not a world wide perfect solution,
  2131. // but a rough attempt for at least US and Western Europe.
  2132. // Pc: Connector punctuation
  2133. // Pd: Dash punctuation
  2134. // Pe: Close punctuation
  2135. // Pf: Final punctuation
  2136. // Pi: Initial punctuation
  2137. // Po: Other punctuation, including ¿?¡!,.:;
  2138. // Ps: Open punctuation
  2139. $text = preg_replace('/\s(\p{Pc}|\p{Pd}|\p{Pe}|\p{Pf}|!|\?|,|\.|:|;)/s', '$1', $text);
  2140. $text = preg_replace('/(\p{Ps}|¿|¡)\s/s', '$1', $text);
  2141. return $text;
  2142. }
  2143. /**
  2144. * Use the list.module's list_allowed_values() to format the
  2145. * field based on its value ($facet).
  2146. *
  2147. * @param $facet string
  2148. * The indexed value
  2149. * @param $options
  2150. * An array of options including the hook_block $delta.
  2151. */
  2152. function apachesolr_fields_list_facet_map_callback($facets, $options) {
  2153. $map = array();
  2154. $allowed_values = array();
  2155. // @see list_field_formatter_view()
  2156. $fields = field_info_fields();
  2157. $field_name = $options['field']['field_name'];
  2158. if (isset($fields[$field_name])) {
  2159. $allowed_values = list_allowed_values($fields[$field_name]);
  2160. }
  2161. if ($fields[$field_name]['type'] == 'list_boolean') {
  2162. // Convert boolean allowed value keys (0, 1, TRUE, FALSE) to
  2163. // Apache Solr representations (string).
  2164. foreach($allowed_values as $key => $value) {
  2165. $strkey = $key ? 'true' : 'false';
  2166. $allowed_values[$strkey] = $value;
  2167. unset($allowed_values[$key]);
  2168. }
  2169. }
  2170. foreach ($facets as $key) {
  2171. if (isset($allowed_values[$key])) {
  2172. $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]);
  2173. }
  2174. elseif ($key === '_empty_' && !empty($options['facet missing allowed'])) {
  2175. // Facet missing.
  2176. $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
  2177. }
  2178. else {
  2179. $map[$key]['#markup'] = field_filter_xss($key);
  2180. }
  2181. // The value has already been filtered.
  2182. $map[$key]['#html'] = TRUE;
  2183. }
  2184. return $map;
  2185. }
  2186. /**
  2187. * @param $facet string
  2188. * The indexed value
  2189. * @param $options
  2190. * An array of options including the hook_block $delta.
  2191. * @see http://drupal.org/node/1059372
  2192. */
  2193. function apachesolr_nodereference_map_callback($facets, $options) {
  2194. $map = array();
  2195. $allowed_values = array();
  2196. // @see list_field_formatter_view()
  2197. $fields = field_info_fields();
  2198. $field_name = $options['field']['field_name'];
  2199. if (isset($fields[$field_name])) {
  2200. $allowed_values = node_reference_potential_references($fields[$field_name]);
  2201. }
  2202. foreach ($facets as $key) {
  2203. if (isset($allowed_values[$key])) {
  2204. $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']);
  2205. }
  2206. elseif ($key === '_empty_' && !empty($options['facet missing allowed'])) {
  2207. // Facet missing.
  2208. $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
  2209. }
  2210. else {
  2211. $map[$key]['#markup'] = field_filter_xss($key);
  2212. }
  2213. // The value has already been filtered.
  2214. $map[$key]['#html'] = TRUE;
  2215. }
  2216. return $map;
  2217. }
  2218. /**
  2219. * @param $facet string
  2220. * The indexed value
  2221. * @param $options
  2222. * An array of options including the hook_block $delta.
  2223. * @see http://drupal.org/node/1059372
  2224. */
  2225. function apachesolr_userreference_map_callback($facets, $options) {
  2226. $map = array();
  2227. $allowed_values = array();
  2228. // @see list_field_formatter_view()
  2229. $fields = field_info_fields();
  2230. $field_name = $options['field']['field_name'];
  2231. if (isset($fields[$field_name])) {
  2232. $allowed_values = user_reference_potential_references($fields[$field_name]);
  2233. }
  2234. foreach ($facets as $key) {
  2235. if (isset($allowed_values[$key])) {
  2236. $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']);
  2237. }
  2238. elseif ($key === '_empty_' && !empty($options['facet missing allowed'])) {
  2239. // Facet missing.
  2240. $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
  2241. }
  2242. else {
  2243. $map[$key]['#markup'] = field_filter_xss($key);
  2244. }
  2245. // The value has already been filtered.
  2246. $map[$key]['#html'] = TRUE;
  2247. }
  2248. return $map;
  2249. }
  2250. /**
  2251. * Mapping callback for entity references.
  2252. */
  2253. function apachesolr_entityreference_facet_map_callback(array $values, array $options) {
  2254. $map = array();
  2255. // Gathers entity ids so we can load multiple entities at a time.
  2256. $entity_ids = array();
  2257. foreach ($values as $value) {
  2258. list($entity_type, $id) = explode(':', $value);
  2259. $entity_ids[$entity_type][] = $id;
  2260. }
  2261. // Loads and maps entities.
  2262. foreach ($entity_ids as $entity_type => $ids) {
  2263. $entities = entity_load($entity_type, $ids);
  2264. foreach ($entities as $id => $entity) {
  2265. $key = $entity_type . ':' . $id;
  2266. $map[$key] = entity_label($entity_type, $entity);
  2267. }
  2268. }
  2269. return $map;
  2270. }
  2271. /**
  2272. * Returns the callback function appropriate for a given entity type/bundle.
  2273. *
  2274. * @param string $entity_type
  2275. * The entity type for which we want to know the approprite callback.
  2276. * @param string $callback
  2277. * The callback for which we want the appropriate function.
  2278. * @param string $bundle
  2279. * If specified, the bundle of the entity in question. Some callbacks may
  2280. * be overridden on a bundle-level. Not specified only the entity-level
  2281. * callback will be checked.
  2282. * @return string
  2283. * The function name for this callback, or NULL if not specified.
  2284. */
  2285. function apachesolr_entity_get_callback($entity_type, $callback, $bundle = NULL) {
  2286. $info = entity_get_info($entity_type);
  2287. // A bundle-specific callback takes precedence over the generic one for the
  2288. // entity type.
  2289. if ($bundle && isset($info['bundles'][$bundle]['apachesolr'][$callback])) {
  2290. $callback_function = $info['bundles'][$bundle]['apachesolr'][$callback];
  2291. }
  2292. elseif (isset($info['apachesolr'][$callback])) {
  2293. $callback_function = $info['apachesolr'][$callback];
  2294. }
  2295. else {
  2296. $callback_function = NULL;
  2297. }
  2298. return $callback_function;
  2299. }
  2300. /**
  2301. * Function to retrieve all the nodes to index.
  2302. * Deprecated but kept for backwards compatibility
  2303. * @param String $namespace
  2304. * @param type $limit
  2305. */
  2306. function apachesolr_get_nodes_to_index($namespace, $limit) {
  2307. $env_id = apachesolr_default_environment();
  2308. // Hardcode node as an entity type
  2309. module_load_include('inc', 'apachesolr', 'apachesolr.index');
  2310. apachesolr_index_get_entities_to_index($env_id, 'node', $limit);
  2311. }
  2312. /**
  2313. * Implements hook_theme().
  2314. */
  2315. function apachesolr_theme() {
  2316. return array(
  2317. /**
  2318. * Returns a list of links generated by apachesolr_sort_link
  2319. */
  2320. 'apachesolr_sort_list' => array(
  2321. 'variables' => array('items' => NULL),
  2322. ),
  2323. /**
  2324. * Returns a link which can be used to search the results.
  2325. */
  2326. 'apachesolr_sort_link' => array(
  2327. 'variables' => array('text' => NULL, 'path' => NULL, 'options' => NULL, 'active' => FALSE, 'direction' => ''),
  2328. ),
  2329. /**
  2330. * Themes the title links in admin settings pages.
  2331. */
  2332. 'apachesolr_settings_title' => array(
  2333. 'variables' => array('env_id' => NULL),
  2334. ),
  2335. );
  2336. }
  2337. /**
  2338. * Implements hook_hook_info().
  2339. */
  2340. function apachesolr_hook_info() {
  2341. $hooks = array(
  2342. 'apachesolr_field_mappings' => array(
  2343. 'group' => 'apachesolr',
  2344. ),
  2345. 'apachesolr_field_mappings_alter' => array(
  2346. 'group' => 'apachesolr',
  2347. ),
  2348. 'apachesolr_query_prepare' => array(
  2349. 'group' => 'apachesolr',
  2350. ),
  2351. 'apachesolr_query_alter' => array(
  2352. 'group' => 'apachesolr',
  2353. ),
  2354. 'apachesolr_search_result_alter' => array(
  2355. 'group' => 'apachesolr',
  2356. ),
  2357. 'apachesolr_environment_delete' => array(
  2358. 'group' => 'apachesolr',
  2359. )
  2360. );
  2361. $hooks['apachesolr_index_document_build'] = array(
  2362. 'group' => 'apachesolr',
  2363. );
  2364. return $hooks;
  2365. }
  2366. /**
  2367. * Implements hook_apachesolr_field_mappings().
  2368. */
  2369. function field_apachesolr_field_mappings() {
  2370. $mappings = array(
  2371. 'list_integer' => array(
  2372. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2373. 'map callback' => 'apachesolr_fields_list_facet_map_callback',
  2374. 'index_type' => 'integer',
  2375. 'facets' => TRUE,
  2376. 'query types' => array('term', 'numeric_range'),
  2377. 'query type' => 'term',
  2378. 'facet missing allowed' => TRUE,
  2379. ),
  2380. 'list_float' => array(
  2381. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2382. 'map callback' => 'apachesolr_fields_list_facet_map_callback',
  2383. 'index_type' => 'float',
  2384. 'facets' => TRUE,
  2385. 'query types' => array('term', 'numeric_range'),
  2386. 'query type' => 'term',
  2387. 'facet missing allowed' => TRUE,
  2388. ),
  2389. 'list_text' => array(
  2390. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2391. 'map callback' => 'apachesolr_fields_list_facet_map_callback',
  2392. 'index_type' => 'string',
  2393. 'facets' => TRUE,
  2394. 'facet missing allowed' => TRUE,
  2395. ),
  2396. 'list_boolean' => array(
  2397. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2398. 'map callback' => 'apachesolr_fields_list_facet_map_callback',
  2399. 'index_type' => 'boolean',
  2400. 'facets' => TRUE,
  2401. 'facet missing allowed' => TRUE,
  2402. ),
  2403. 'number_integer' => array(
  2404. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2405. 'index_type' => 'tint',
  2406. 'facets' => TRUE,
  2407. 'query types' => array('term', 'numeric_range'),
  2408. 'query type' => 'term',
  2409. 'facet mincount allowed' => TRUE,
  2410. ),
  2411. 'number_decimal' => array(
  2412. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2413. 'index_type' => 'tfloat',
  2414. 'facets' => TRUE,
  2415. 'query types' => array('term', 'numeric_range'),
  2416. 'query type' => 'term',
  2417. 'facet mincount allowed' => TRUE,
  2418. ),
  2419. 'number_float' => array(
  2420. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2421. 'index_type' => 'tfloat',
  2422. 'facets' => TRUE,
  2423. 'query types' => array('term', 'numeric_range'),
  2424. 'query type' => 'term',
  2425. 'facet mincount allowed' => TRUE,
  2426. ),
  2427. 'taxonomy_term_reference' => array(
  2428. 'map callback' => 'facetapi_map_taxonomy_terms',
  2429. 'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy',
  2430. 'indexing_callback' => array('apachesolr_term_reference_indexing_callback'),
  2431. 'index_type' => 'integer',
  2432. 'facet_block_callback' => 'apachesolr_search_taxonomy_facet_block',
  2433. 'facets' => TRUE,
  2434. 'query types' => array('term'),
  2435. 'query type' => 'term',
  2436. 'facet mincount allowed' => TRUE,
  2437. ),
  2438. 'text' => array(
  2439. 'indexing_callback' => array('apachesolr_fields_default_indexing_callback'),
  2440. 'index_type' => 'string',
  2441. 'facets' => TRUE,
  2442. 'facet missing allowed' => TRUE,
  2443. ),
  2444. );
  2445. return $mappings;
  2446. }
  2447. /**
  2448. * Implements hook_apachesolr_field_mappings() on behalf of date module.
  2449. */
  2450. function date_apachesolr_field_mappings() {
  2451. $mappings = array();
  2452. $default = array(
  2453. 'indexing_callback' => array('apachesolr_date_default_indexing_callback'),
  2454. 'index_type' => 'date',
  2455. 'facets' => TRUE,
  2456. 'query types' => array('date'),
  2457. 'query type' => 'date',
  2458. 'min callback' => 'apachesolr_get_min_date',
  2459. 'max callback' => 'apachesolr_get_max_date',
  2460. 'map callback' => 'facetapi_map_date',
  2461. );
  2462. // DATE and DATETIME fields can use the same indexing callback.
  2463. $mappings['date'] = $default;
  2464. $mappings['datetime'] = $default;
  2465. // DATESTAMP fields need a different callback.
  2466. $mappings['datestamp'] = $default;
  2467. $mappings['datestamp']['indexing_callback'] = array('apachesolr_datestamp_default_indexing_callback');
  2468. return $mappings;
  2469. }
  2470. /**
  2471. * Callback that returns the minimum date of the facet's datefield.
  2472. *
  2473. * @param $facet
  2474. * An array containing the facet definition.
  2475. *
  2476. * @return
  2477. * The minimum time in the node table.
  2478. *
  2479. * @todo Cache this value.
  2480. */
  2481. function apachesolr_get_min_date(array $facet) {
  2482. // FieldAPI date fields.
  2483. $table = 'field_data_' . $facet['field api name'];
  2484. $column = $facet['field api name'] . '_value';
  2485. $query = db_select($table, 't');
  2486. $query->addExpression('MIN(' . $column . ')', 'min');
  2487. $query_min = $query->execute()->fetch()->min;
  2488. // Update to unix timestamp if this is an ISO or other format.
  2489. if (is_numeric($query_min)) {
  2490. $return = (int)$query_min;
  2491. }
  2492. else {
  2493. $return = strtotime($query_min);
  2494. if ($return === FALSE) {
  2495. // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00').
  2496. // Return default start date of 1 as the date query type getDateRange()
  2497. // function expects a non-0 integer.
  2498. $return = 1;
  2499. }
  2500. }
  2501. return $return;
  2502. }
  2503. /**
  2504. * Callback that returns the maximum value of the facet's date field.
  2505. *
  2506. * @param $facet
  2507. * An array containing the facet definition.
  2508. *
  2509. * @return
  2510. * The maximum time of the field.
  2511. *
  2512. * @todo Cache this value.
  2513. */
  2514. function apachesolr_get_max_date(array $facet) {
  2515. // FieldAPI date fields.
  2516. $table = 'field_data_' . $facet['field api name'];
  2517. $column = $facet['field api name'] . '_value';
  2518. $query = db_select($table, 't');
  2519. $query->addExpression('MAX(' . $column . ')', 'max');
  2520. $query_max = $query->execute()->fetch()->max;
  2521. // Update to unix timestamp if this is an ISO or other format.
  2522. if (is_numeric($query_max)) {
  2523. $return = (int)$query_max;
  2524. }
  2525. else {
  2526. $return = strtotime($query_max);
  2527. if ($return === FALSE) {
  2528. // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00').
  2529. // Return default end date of 1 year from now.
  2530. $return = time() + (52 * 7 * 24 * 60 * 60);
  2531. }
  2532. }
  2533. return $return;
  2534. }
  2535. /**
  2536. * Implements hook_apachesolr_field_mappings() on behalf of References (node_reference).
  2537. * @see http://drupal.org/node/1059372
  2538. */
  2539. function node_reference_apachesolr_field_mappings() {
  2540. $mappings = array(
  2541. 'node_reference' => array(
  2542. 'indexing_callback' => array('apachesolr_nodereference_indexing_callback'),
  2543. 'index_type' => 'integer',
  2544. 'map callback' => 'apachesolr_nodereference_map_callback',
  2545. 'facets' => TRUE,
  2546. )
  2547. );
  2548. return $mappings;
  2549. }
  2550. /**
  2551. * Implements hook_apachesolr_field_mappings() on behalf of References (user_reference).
  2552. * @see http://drupal.org/node/1059372
  2553. */
  2554. function user_reference_apachesolr_field_mappings() {
  2555. $mappings = array(
  2556. 'user_reference' => array(
  2557. 'indexing_callback' => array('apachesolr_userreference_indexing_callback'),
  2558. 'index_type' => 'integer',
  2559. 'map callback' => 'apachesolr_userreference_map_callback',
  2560. 'facets' => TRUE,
  2561. ),
  2562. );
  2563. return $mappings;
  2564. }
  2565. /**
  2566. * Implements hook_apachesolr_field_mappings() on behalf of EntityReferences (entityreference)
  2567. * @see http://drupal.org/node/1572722
  2568. */
  2569. function entityreference_apachesolr_field_mappings() {
  2570. $mappings = array(
  2571. 'entityreference' => array(
  2572. 'indexing_callback' => array('apachesolr_entityreference_indexing_callback'),
  2573. 'map callback' => 'apachesolr_entityreference_facet_map_callback',
  2574. 'index_type' => 'string',
  2575. 'facets' => TRUE,
  2576. 'query types' => array('term'),
  2577. 'facet missing allowed' => TRUE,
  2578. ),
  2579. );
  2580. return $mappings;
  2581. }
  2582. /**
  2583. * A replacement for l()
  2584. * - doesn't add the 'active' class
  2585. * - retains all $_GET parameters that ApacheSolr may not be aware of
  2586. * - if set, $options['query'] MUST be an array
  2587. *
  2588. * @see http://api.drupal.org/api/function/l/6
  2589. * for parameters and options.
  2590. *
  2591. * @return
  2592. * an HTML string containing a link to the given path.
  2593. */
  2594. function apachesolr_l($text, $path, $options = array()) {
  2595. // Merge in defaults.
  2596. $options += array(
  2597. 'attributes' => array(),
  2598. 'html' => FALSE,
  2599. 'query' => array(),
  2600. );
  2601. // Don't need this, and just to be safe.
  2602. unset($options['attributes']['title']);
  2603. // Retain GET parameters that Apache Solr knows nothing about.
  2604. $get = array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1), $options['query']);
  2605. $options['query'] += $get;
  2606. return '<a href="' . check_url(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain(html_entity_decode($text))) . '</a>';
  2607. }
  2608. function theme_apachesolr_sort_link($vars) {
  2609. $icon = '';
  2610. if ($vars['direction']) {
  2611. $icon = ' ' . theme('tablesort_indicator', array('style' => $vars['direction']));
  2612. }
  2613. if ($vars['active']) {
  2614. if (isset($vars['options']['attributes']['class'])) {
  2615. $vars['options']['attributes']['class'] .= ' active';
  2616. }
  2617. else {
  2618. $vars['options']['attributes']['class'] = 'active';
  2619. }
  2620. }
  2621. return $icon . apachesolr_l($vars['text'], $vars['path'], $vars['options']);
  2622. }
  2623. function theme_apachesolr_sort_list($vars) {
  2624. // theme('item_list') expects a numerically indexed array.
  2625. $vars['items'] = array_values($vars['items']);
  2626. return theme('item_list', array('items' => $vars['items']));
  2627. }
  2628. /**
  2629. * Themes the title for settings pages.
  2630. */
  2631. function theme_apachesolr_settings_title($vars) {
  2632. $output = '';
  2633. // Gets environment information, builds header with nested link to the environment's
  2634. // edit page. Skips building title if environment info could not be retrieved.
  2635. if ($environment = apachesolr_environment_load($vars['env_id'])) {
  2636. $url = url(
  2637. 'admin/config/search/apachesolr/settings/',
  2638. array('query' => array('destination' => current_path()))
  2639. );
  2640. $output .= '<h3>';
  2641. $output .= t(
  2642. 'Settings for: @environment (<a href="@url">Overview</a>)',
  2643. array('@url' => $url, '@environment' => $environment['name'])
  2644. );
  2645. $output .= "</h3>\n";
  2646. }
  2647. return $output;
  2648. }
  2649. /**
  2650. * Export callback to load the view subrecords, which are the index bundles.
  2651. */
  2652. function apachesolr_environment_load_subrecords(&$environments) {
  2653. if (empty($environments)) {
  2654. // Nothing to do.
  2655. return NULL;
  2656. }
  2657. $all_index_bundles = db_select('apachesolr_index_bundles', 'ib')
  2658. ->fields('ib', array('env_id', 'entity_type', 'bundle'))
  2659. ->condition('env_id', array_keys($environments), 'IN')
  2660. ->orderBy('env_id')
  2661. ->orderBy('entity_type')
  2662. ->orderBy('bundle')
  2663. ->execute()
  2664. ->fetchAll(PDO::FETCH_ASSOC);
  2665. $all_index_bundles_keyed = array();
  2666. foreach ($all_index_bundles as $env_info) {
  2667. extract($env_info);
  2668. $all_index_bundles_keyed[$env_id][$entity_type][] = $bundle;
  2669. }
  2670. $all_variables = db_select('apachesolr_environment_variable', 'v')
  2671. ->fields('v', array('env_id', 'name', 'value'))
  2672. ->condition('env_id', array_keys($environments), 'IN')
  2673. ->orderBy('env_id')
  2674. ->orderBy('name')
  2675. ->orderBy('value')
  2676. ->execute()
  2677. ->fetchAll(PDO::FETCH_ASSOC);
  2678. $variables = array();
  2679. foreach ($all_variables as $variable) {
  2680. extract($variable);
  2681. $variables[$env_id][$name] = unserialize($value);
  2682. }
  2683. foreach ($environments as $env_id => &$environment) {
  2684. $index_bundles = !empty($all_index_bundles_keyed[$env_id]) ? $all_index_bundles_keyed[$env_id] : array();
  2685. $conf = !empty($variables[$env_id]) ? $variables[$env_id] : array();
  2686. if (is_array($environment)) {
  2687. // Environment is an array.
  2688. // If we have different values in the database compared with what we
  2689. // have in the given environment argument we allow the admin to revert
  2690. // the db values so we can stick with a consistent system
  2691. if (!empty($environment['index_bundles']) && !empty($index_bundles) && $environment['index_bundles'] !== $index_bundles) {
  2692. unset($environment['in_code_only']);
  2693. $environment['type'] = 'Overridden';
  2694. }
  2695. if (!empty($environment['conf']) && !empty($conf) && !apachesolr_environment_conf_equals($environment['conf'], $conf)) {
  2696. unset($environment['in_code_only']);
  2697. $environment['type'] = 'Overridden';
  2698. }
  2699. $environment['index_bundles'] = (empty($environment['index_bundles']) || !empty($index_bundles)) ? $index_bundles : $environment['index_bundles'];
  2700. $environment['conf'] = (empty($environment['conf']) || !empty($conf)) ? $conf : $environment['conf'];
  2701. }
  2702. elseif (is_object($environment)) {
  2703. // Environment is an object.
  2704. if ($environment->index_bundles !== $index_bundles && !empty($index_bundles)) {
  2705. unset($environment->in_code_only);
  2706. $environment->type = 'Overridden';
  2707. }
  2708. if (!apachesolr_environment_conf_equals($environment->conf, $conf) && !empty($conf)) {
  2709. unset($environment->in_code_only);
  2710. $environment->type = 'Overridden';
  2711. }
  2712. $environment->index_bundles = (empty($environment->index_bundles) || !empty($index_bundles)) ? $index_bundles : $environment->index_bundles;
  2713. $environment->conf = (empty($environment->conf) || !empty($conf)) ? $conf : $environment->conf;
  2714. }
  2715. }
  2716. }
  2717. /**
  2718. * Determines whether two configuration arrays contain the same keys and
  2719. * values.
  2720. *
  2721. * This is used to compare in-code and in-database configuration of ctools
  2722. * exportables.
  2723. *
  2724. * @see apachesolr_environment_load_subrecords()
  2725. *
  2726. * @param array $conf1
  2727. * First configuration array.
  2728. * @param array $conf2
  2729. * Second configuration array.
  2730. * @param bool $ignore_state
  2731. * Whether to ignore state variables, such as `apachesolr_index_last`,
  2732. * `apachesolr_index_updated`, and `apachesolr_last_optimize`.
  2733. *
  2734. * @return TRUE if both arrays are considered equal, FALSE otherwise.
  2735. */
  2736. function apachesolr_environment_conf_equals(array $conf1, array $conf2, $ignore_state = TRUE) {
  2737. if ($ignore_state === TRUE) {
  2738. // Strip out state variables before comparing both arrays.
  2739. $state_variables = apachesolr_environment_conf_state_variables();
  2740. $conf1 = array_diff_key($conf1, $state_variables);
  2741. $conf2 = array_diff_key($conf2, $state_variables);
  2742. }
  2743. return $conf1 === $conf2;
  2744. }
  2745. /**
  2746. * Returns a list of key names for environment variables indicating state
  2747. * instead of configuration, e.g. `apachesolr_index_last`,
  2748. * `apachesolr_index_updated`, and `apachesolr_last_optimize`.
  2749. */
  2750. function apachesolr_environment_conf_state_variables() {
  2751. $keys = &drupal_static(__FUNCTION__);
  2752. if (!isset($keys)) {
  2753. $_keys = array(
  2754. 'apachesolr_index_last',
  2755. 'apachesolr_index_updated',
  2756. 'apachesolr_last_optimize',
  2757. );
  2758. // Use array_combine() to make it easier to use in array_diff_key().
  2759. // @see apachesolr_environment_conf_equals()
  2760. $keys = array_combine($_keys, $_keys);
  2761. }
  2762. return $keys;
  2763. }
  2764. /**
  2765. * Callback for saving Apache Solr environment CTools exportables.
  2766. *
  2767. * CTools uses objects, while Apache Solr uses arrays; turn CTools value into an
  2768. * array, then call the normal save function.
  2769. *
  2770. * @param stdclass $environment
  2771. * An environment object.
  2772. */
  2773. function apachesolr_ctools_environment_save($environment) {
  2774. apachesolr_environment_save((array) $environment);
  2775. }
  2776. /**
  2777. * Callback for reverting Apache Solr environment CTools exportables.
  2778. *
  2779. * @param mixed $env_id
  2780. * An environment machine name. CTools may provide an id OR a complete
  2781. * environment object; Since Apache Solr loads environments as arrays, this
  2782. * may also be an environment array.
  2783. */
  2784. function apachesolr_ctools_environment_delete($env_id) {
  2785. if (is_object($env_id) || is_array($env_id)) {
  2786. $env_id = (object) $env_id;
  2787. $env_id = $env_id->env_id;
  2788. }
  2789. apachesolr_environment_delete($env_id);
  2790. }
  2791. /**
  2792. * Callback for exporting Apache Solr environments as CTools exportables.
  2793. *
  2794. * @param array $environment
  2795. * An environment array from Apache Solr.
  2796. * @param string $indent
  2797. * White space for indentation from CTools.
  2798. */
  2799. function apachesolr_ctools_environment_export($environment, $indent) {
  2800. ctools_include('export');
  2801. $environment = (object) $environment;
  2802. // Re-load the enviroment, since in some cases the conf
  2803. // is stripped since it's not in the actual schema.
  2804. $environment = (object) apachesolr_environment_load($environment->env_id);
  2805. $index_bundles = array();
  2806. foreach (entity_get_info() as $type => $info) {
  2807. if ($bundles = apachesolr_get_index_bundles($environment->env_id, $type)) {
  2808. $index_bundles[$type] = $bundles;
  2809. }
  2810. }
  2811. // Remove variable values related to state from code.
  2812. $state_variables = apachesolr_environment_conf_state_variables();
  2813. foreach ($state_variables as $key) {
  2814. unset($environment->conf[$key]);
  2815. }
  2816. $additions_top = array();
  2817. $additions_bottom = array('conf' => $environment->conf, 'index_bundles' => $index_bundles);
  2818. return ctools_export_object('apachesolr_environment', $environment, $indent, NULL, $additions_top, $additions_bottom);
  2819. }