first import 7.x-1.4
This commit is contained in:
		
							
								
								
									
										338
									
								
								CHANGELOG.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								CHANGELOG.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,338 @@
 | 
			
		||||
Search API 1.x, dev (xx/xx/xxxx):
 | 
			
		||||
---------------------------------
 | 
			
		||||
 | 
			
		||||
Search API 1.4 (01/09/2013):
 | 
			
		||||
----------------------------
 | 
			
		||||
- #1827272 by drunken monkey: Fixed regression introduced by #1777710.
 | 
			
		||||
- #1807622 by drunken monkey: Fixed definition of the default node index.
 | 
			
		||||
- #1818948 by das-peter: Fixed endless loop in
 | 
			
		||||
  search_api_index_specific_items_delayed().
 | 
			
		||||
- #1406808 by Haza, drunken monkey: Added support for date popup in exposed
 | 
			
		||||
  filters.
 | 
			
		||||
- #1823916 by aschiwi: Fixed batch_sise typos.
 | 
			
		||||
 | 
			
		||||
Search API 1.3 (10/10/2012):
 | 
			
		||||
----------------------------
 | 
			
		||||
- Patch by mr.baileys: Fixed "enable" function doesn't use security tokens.
 | 
			
		||||
- #1318904 by becw, das-peter, orakili, drunken monkey: Added improved handling
 | 
			
		||||
  for NULL values in Views.
 | 
			
		||||
- #1306008 by Damien Tournoud, drunken monkey: Fixed handling of negative
 | 
			
		||||
  facets.
 | 
			
		||||
- #1182912 by drunken monkey, sepgil: Added Rules action for indexing entities.
 | 
			
		||||
- #1507882 by jsacksick: Added "Exclude unpublished nodes" data alteration.
 | 
			
		||||
- #1225620 by drunken monkey: Added Batch API integration for the "Index now"
 | 
			
		||||
  functionality.
 | 
			
		||||
- #1777710 by dasjo: Remove dependency on $_GET['q'] for determining base paths.
 | 
			
		||||
- #1715238 by jsacksick: Fixed fulltext argument handler field list is broken.
 | 
			
		||||
- #1414138 by drunken monkey: Fixed internal static index property cache.
 | 
			
		||||
- #1253320 by drunken monkey, fago: Fixed improper error handling.
 | 
			
		||||
 | 
			
		||||
Search API 1.2 (07/07/2012):
 | 
			
		||||
----------------------------
 | 
			
		||||
- #1368548 by das-peter: Do not index views results by entity id.
 | 
			
		||||
- #1422750 by drunken monkey, sepgil: Fixed illegal modification of entity
 | 
			
		||||
  objects.
 | 
			
		||||
- #1363114 by nbucknor: Fixed inclusion of upper bound in range facets.
 | 
			
		||||
- #1580780 by drunken monkey: Fixed default regexps of the Tokenizer.
 | 
			
		||||
- #1468678 by znerol: Fixed erroneous use of Drupal multibyte wrapper functions.
 | 
			
		||||
- #1600986 by DamienMcKenna: Fixed dependencies of exported search servers.
 | 
			
		||||
- #1569874 by cpliakas: Fixed removal/adding of facets when indexed fields are
 | 
			
		||||
  changed.
 | 
			
		||||
- #1528436 by jsacksick, drunken monkey: Fixed handling of exportable entities.
 | 
			
		||||
 | 
			
		||||
Search API 1.1 (05/23/2012):
 | 
			
		||||
----------------------------
 | 
			
		||||
- Fixed escaping of error messages.
 | 
			
		||||
- #1330506 by drunken monkey: Removed the old Facets module.
 | 
			
		||||
- #1504318 by peximo: Fixed Views pager offset.
 | 
			
		||||
- #1141488 by moonray, drunken monkey: Added option to use multiple values with
 | 
			
		||||
  contextual filters.
 | 
			
		||||
- #1535726 by bojanz, joelpittet: Fixed arguments for
 | 
			
		||||
  $service->configurationFormValidate() for empty forms.
 | 
			
		||||
- #1400882 by mh86: Fixed "Index hierarchy" for "All parents".
 | 
			
		||||
 | 
			
		||||
Search API 1.0 (12/15/2011):
 | 
			
		||||
----------------------------
 | 
			
		||||
- #1350322 by drunken monkey: Fixed regressions introduced with cron queue
 | 
			
		||||
  indexing.
 | 
			
		||||
- #1352292 by das-peter, drunken monkey: Use Search API specific table groups in
 | 
			
		||||
  Views integration.
 | 
			
		||||
- #1351524 by das-peter: Made Views result row indexing more robust.
 | 
			
		||||
- #1194362 by Damien Tournoud: Fixed click sort added to non-existent Views
 | 
			
		||||
  fields.
 | 
			
		||||
- #1347150 by drunken monkey: Fixed fields access of Views facets block display.
 | 
			
		||||
- #1345972 by Krasnyj, drunken monkey: Added handling of large item amounts to
 | 
			
		||||
  trackItemInsert().
 | 
			
		||||
- #1324182 by dereine, drunken monkey: Fixed indexing author when node access is
 | 
			
		||||
  enabled.
 | 
			
		||||
- #1215526 by cpliakas, drunken monkey: Added support for the "Bundle" facet
 | 
			
		||||
  dependency plugin.
 | 
			
		||||
- #1337292 by drunken monkey: Fixed facet dependency system.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, RC 1 (11/10/2011):
 | 
			
		||||
----------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #1260834 by drunken monkey: Added a way to define custom data types.
 | 
			
		||||
- #1308638 by drunken monkey: Reduce size of stored index settings.
 | 
			
		||||
- #1291346 by drunken monkey: Expose SearchApiQuery::preExecute() and
 | 
			
		||||
  postExecute().
 | 
			
		||||
- #955088 by dereine, drunken monkey: Provide (additional) access functionality.
 | 
			
		||||
- #1064884 by drunken monkey: Added support for indexing non-entities.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1304026 by drunken monkey: Utilize Facet API's 'include default facets' key
 | 
			
		||||
  in searcher definitions.
 | 
			
		||||
- #1231512 by drunken monkey: Use real Relationships instead of level magic in
 | 
			
		||||
  Views integration.
 | 
			
		||||
- #1260768 by drunken monkey: Move "Search pages" into its own project.
 | 
			
		||||
- #1260812 by drunken monkey: Move "Database search" into its own project.
 | 
			
		||||
- #1287602 by drunken monkey: Fixed „Index items immediately“ to delay indexing
 | 
			
		||||
  on insert, too.
 | 
			
		||||
- #1319500 by drunken monkey: Remove items after unsuccessful loads.
 | 
			
		||||
- #1297958 by drunken monkey: Fixed wrong facet operator used for range facets.
 | 
			
		||||
- #1305736 by drunken monkey: Fixed notice for unset Views group operator.
 | 
			
		||||
- #1263214 by drunken monkey: Fixed indexing with „Index items immediately“
 | 
			
		||||
  indexes old entity state.
 | 
			
		||||
- #1228726 by drunken monkey, mh86: Increased size of 'options' fields in
 | 
			
		||||
  database.
 | 
			
		||||
- #1295144 by katbailey: Added alter hook for Facet API search keys.
 | 
			
		||||
- #1294828 by drunken monkey: Fixed accidental presence of good OOP coding
 | 
			
		||||
  standards in Views integration.
 | 
			
		||||
- #1291376 by drunken monkey: Expose
 | 
			
		||||
  SearchApiFacetapiAdapter::getCurrentSearch().
 | 
			
		||||
- #1198764 by morningtime, drunken monkey: Fixed handling of Views filter
 | 
			
		||||
  groups.
 | 
			
		||||
- #1286500 by drunken monkey: Fixed „Search IDs” setting for facets not saved.
 | 
			
		||||
- #1278780 by dereine, drunken monkey: Fixed status field requirement for node
 | 
			
		||||
  access.
 | 
			
		||||
- #1182614 by katbailey, cpliakas, drunken monkey, thegreat, das-peter: Added
 | 
			
		||||
  Facet API integration.
 | 
			
		||||
- #1278592 by das-peter: Fixed default view mode for non-entites or entities
 | 
			
		||||
  without view modes.
 | 
			
		||||
- #1251674 by Nick_vh: Fixed handling of empty fulltext keys in Views.
 | 
			
		||||
- #1145306 by Nick_vh, drunken monkey: Fixed handling of multiple filters in the
 | 
			
		||||
  database service class.
 | 
			
		||||
- #1264164 by das-peter: Fixed the definition of the external data source
 | 
			
		||||
  controller's trackItemChange() method.
 | 
			
		||||
- #1262362 by drunken monkey: Fixed error handling for orphaned facets.
 | 
			
		||||
- #1233426 by atlea: Fixed dirty and queued items don't get removed from the
 | 
			
		||||
  tracking table when deleted.
 | 
			
		||||
- #1258240 by drunken monkey: Fixed more overlooked entity type assumptions.
 | 
			
		||||
- #1213698 by drunken monkey: Added a data alteration for indexing complete
 | 
			
		||||
  hierarchies.
 | 
			
		||||
- #1252208 by tedfordgif: Fixed superfluous query chars in active facet links.
 | 
			
		||||
- #1224564 by drunken monkey: Added user language as a filter in Views.
 | 
			
		||||
- #1242614 by jsacksick: Fixed division by zero in drush_search_api_status().
 | 
			
		||||
- #1250168 by drunken monkey: Fixed deleted items aren't removed from servers.
 | 
			
		||||
- #1236642 by jsacksick, drunken monkey: Fixed stale static cache of
 | 
			
		||||
  search_api_get_item_type_info().
 | 
			
		||||
- #1237348 by drunken monkey: Added a "Language control" data alteration.
 | 
			
		||||
- #1214846 by drunken monkey, Kender: Fixed overlong table names when DB prefix
 | 
			
		||||
  is used.
 | 
			
		||||
- #1232478 by Damien Tournoud, drunken monkey: Fixed update of field type
 | 
			
		||||
  settings for DB backend and index.
 | 
			
		||||
- #1229772 by drunken monkey: Fixed order in which items are indexed.
 | 
			
		||||
- #946624 by drunken monkey: Adapted to use a cron queue for indexing.
 | 
			
		||||
- #1217702 by Amitaibu, drunken monkey: Added documentation on facet URLs.
 | 
			
		||||
- #1214862 by drunken monkey: Added bundle-specific fields for related entities.
 | 
			
		||||
- #1204964 by klausi: Fixed default index status is not overridden on saving.
 | 
			
		||||
- #1191442 by drunken monkey: Fixed facets block view showing only tid.
 | 
			
		||||
- #1161532 by drunken monkey: Fixed discerning between delete and revert in
 | 
			
		||||
  hook_*_delete().
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 10 (06/20/2011):
 | 
			
		||||
-------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #1068342 by drunken monkey: Added a 'fields to run on' option for processors.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1190086 by drunken monkey: Fixed crash in hook_entity_insert().
 | 
			
		||||
- #1190324 by drunken monkey: Adapted to API change in Entity API.
 | 
			
		||||
- #1168684 by drunken monkey: Added improved tokenizer defaults for English.
 | 
			
		||||
- #1163096 by drunken monkey: Fixed cached types for DB servers aren't correctly
 | 
			
		||||
  updated.
 | 
			
		||||
- #1133864 by agentrickard, awolfey, greg.1.anderson, drunken monkey: Added
 | 
			
		||||
  Drush integration.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 9 (06/06/2011):
 | 
			
		||||
------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #1089758 by becw, drunken monkey: Updated Views field handlers to utilize new
 | 
			
		||||
  features.
 | 
			
		||||
- #1150260 by drunken monkey: Added a way to let processors and data alterations
 | 
			
		||||
  decide on which indexes they can run.
 | 
			
		||||
- #1138992 by becw, drunken monkey: Added read-only indexes.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1179990 by j0rd: Fixed facet blocks don't correctly respect the "unlimited"
 | 
			
		||||
  setting.
 | 
			
		||||
- #1138650 by klausi, Amitaibu, drunken monkey: Fixed PHP strict warnings.
 | 
			
		||||
- #1111852 by miiimooo, drunken monkey: Added a 'More like this' feature.
 | 
			
		||||
- #1171360 by jbguerraz, drunken monkey: Added possibility to restrict the
 | 
			
		||||
  options to display in an exposed options filter.
 | 
			
		||||
- #1161676 by awolfey, drunken monkey: Added Stopwords processor.
 | 
			
		||||
- #1166514 by drunken monkey: Fixed parseKeys() to handle incomplete quoting.
 | 
			
		||||
- #1161966 by JoeMcGuire: Added Search API Spellcheck support for Pages.
 | 
			
		||||
- #1118416 by drunken monkey: Added option to index entities instantly after
 | 
			
		||||
  they are saved.
 | 
			
		||||
- #1153298 by JoeMcGuire, drunken monkey: Added option getter and setter to
 | 
			
		||||
  Views query handler.
 | 
			
		||||
- #1147466 by awolfey: Added excerpt Views field.
 | 
			
		||||
- #1152432 by morningtime: Fixed strict warnings in render() functions.
 | 
			
		||||
- #1144400 by drunken monkey: Fixed use of entity_exportable_schema_fields() in
 | 
			
		||||
  hook_schema().
 | 
			
		||||
- #1141206 by drunken monkey: Fixed "quantity" variable for Search page pager.
 | 
			
		||||
- #1117074 by drunken monkey: Fixed handling of overlong tokens by DB backend.
 | 
			
		||||
- #1124548 by drunken monkey: Fixed syntax error in search_api.admin.inc.
 | 
			
		||||
- #1134296 by klausi: Fixed check for NULL in SearchApiDbService->convert().
 | 
			
		||||
- #1123604 by drunken monkey, fago: Added generalized "aggregation" data
 | 
			
		||||
  alteration.
 | 
			
		||||
- #1129226 by drunken monkey: Fixed incorrect handling of facets deactivated for
 | 
			
		||||
  some search IDs.
 | 
			
		||||
- #1086492 by drunken monkey: Fixed inadequate warnings when using the "Facets
 | 
			
		||||
  block" display with wrong base table.
 | 
			
		||||
- #1109308 by drunken monkey : Added option to choose between display of field
 | 
			
		||||
  or facet name in "Current search" block.
 | 
			
		||||
- #1120850 by drunken monkey, fangel: Fixed type of related entities in nested
 | 
			
		||||
  lists.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 8 (04/02/2011):
 | 
			
		||||
------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #1012878 by drunken monkey: Added a way to index an entity directly.
 | 
			
		||||
- #1109130 by drunken monkey: Added better structure for Views field rendering.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1018384 by drunken monkey: Fixed Views field names to not contain colons.
 | 
			
		||||
- #1105704 by drunken monkey: Fixed exposed sorts always sort on 'top' sort.
 | 
			
		||||
- #1104056 by drunken monkey: Added "Current search" support for non-facet
 | 
			
		||||
  filters.
 | 
			
		||||
- #1103814 by drunken monkey: Fixed missing argument for extractFields().
 | 
			
		||||
- #1081084 by drunken monkey: Fixed notices in add_fulltext_field alteration.
 | 
			
		||||
- #1091074 by drunken monkey, ygerasimov: Added machine names to "related
 | 
			
		||||
  entities" list.
 | 
			
		||||
- #1066278 by ygerasimov, drunken monkey: Removed
 | 
			
		||||
  search_api_facets_by_block_status().
 | 
			
		||||
- #1081666 by danielnolde: Fixed PHP notices when property labels are missing.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 7 (03/08/2011):
 | 
			
		||||
------------------------------------
 | 
			
		||||
- #1083828 by drunken monkey: Added documentation on indexing custom data.
 | 
			
		||||
- #1081244 by drunken monkey: Fixed debug line still contained in DB backend.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 6 (03/04/2011):
 | 
			
		||||
------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #1075810 by drunken monkey: Added API function for marking entities as dirty.
 | 
			
		||||
- #1043456 by drunken monkey: Added form validation/submission for plugins.
 | 
			
		||||
- #1048032 by drunken monkey: Added a hook for altering the indexed items.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1068334 by drunken monkey: Added a data alteration for indexing the viewed
 | 
			
		||||
  entity.
 | 
			
		||||
- #1080376 by drunken monkey: Fixed "Current search" block field names.
 | 
			
		||||
- #1076170 by drunken monkey: Added a Views display plugin for facet blocks.
 | 
			
		||||
- #1064518 by drunken monkey: Added support for entity-based Views handlers.
 | 
			
		||||
- #1080004 by drunken monkey: Fixed confusing "Current search" block layout.
 | 
			
		||||
- #1071894 by drunken monkey: Fixed incorrect handling of boolean facets.
 | 
			
		||||
- #1078590 by fago: Added check to skip default index creation when installed
 | 
			
		||||
  via installation profile.
 | 
			
		||||
- #1018366 by drunken monkey: Added option to hide active facet links.
 | 
			
		||||
- #1058410 by drunken monkey: Added overhauled display of search results.
 | 
			
		||||
- #1013632 by drunken monkey: Added facet support for the database backend.
 | 
			
		||||
- #1069184: "Current search" block passes query parameters wrongly.
 | 
			
		||||
- #1038016 by fago: Error upon indexing inaccessible list properties.
 | 
			
		||||
- #1005532: Adaption to Entity API change (new optionsList() parameter).
 | 
			
		||||
- #1057224 by TimLeytens: Problem with entity_uri('file').
 | 
			
		||||
- #1051286: Show type/boost options only for indexed fields.
 | 
			
		||||
- #1049978: Provide a "More" link for facets.
 | 
			
		||||
- #1039250: Improve facet block titles.
 | 
			
		||||
- #1043492: Problems with default (exported) entities.
 | 
			
		||||
- #1037916 by fago: Updates must not call API functions.
 | 
			
		||||
- #1032708 by larskleiner: Notice: Undefined variable: blocks.
 | 
			
		||||
- #1032404 by larskleiner: Notice: Undefined index: fields.
 | 
			
		||||
- #1032032 by pillarsdotnet: search_api_enable() aborts with a database error
 | 
			
		||||
  on install.
 | 
			
		||||
- #1026496: status doesn't get set properly when creating entities.
 | 
			
		||||
- #1027992 by TimLeytens: Filter indexed items based on bundle.
 | 
			
		||||
- #1024194 by TimLeytens: Provide a search block for each page.
 | 
			
		||||
- #1028042: Change {search_api_item}.index_id back to an INT.
 | 
			
		||||
- #1021664: Paged views results empty when adding facet.
 | 
			
		||||
- #872912: Write tests.
 | 
			
		||||
- #1013018: Make the "Fulltext field" data alteration more useful.
 | 
			
		||||
- #1024514: Error when preprocessing muli-valued fulltext fields.
 | 
			
		||||
- #1020372: CSS classes for facets.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 5 (01/05/2011):
 | 
			
		||||
------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #917998: Enhance data alterations by making them objects.
 | 
			
		||||
- #991632: Incorporate newly available entity hooks.
 | 
			
		||||
- #963062: Make facets exportable.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #1018544: includes/entity.inc mentioned in a few places.
 | 
			
		||||
- #1011458: Move entity and processor classes into individual files.
 | 
			
		||||
- #1012478: HTML in node bodies is escaped in Views.
 | 
			
		||||
- #1014548: Add newly required database fields for entities.
 | 
			
		||||
- #915174: Remove unnecessary files[] declarations from .info files.
 | 
			
		||||
- #988780: Merge of entity modules.
 | 
			
		||||
- #997852: Service config oddities.
 | 
			
		||||
- #994948: "Add index" results in blank page.
 | 
			
		||||
- #993470: Unnecessary warning when no valid keys or filters are given.
 | 
			
		||||
- #986412: Notice: Undefined index: value in theme_search_api_page_result().
 | 
			
		||||
- #987928: EntityDBExtendable::__sleep() is gone.
 | 
			
		||||
- #985324: Add "Current search" block.
 | 
			
		||||
- #984174: Bug in Index::prepareProcessors() when processors have not been set.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 4 (11/29/2010):
 | 
			
		||||
------------------------------------
 | 
			
		||||
API changes:
 | 
			
		||||
- #976876: Move Solr module into its own project.
 | 
			
		||||
- #962582: Cross-entity searches (API addition).
 | 
			
		||||
- #939482 by fago: Fix exportables.
 | 
			
		||||
- #939092: Several API changes regarding service class methods.
 | 
			
		||||
- #939414: Enhanced service class descriptions. [soft API change]
 | 
			
		||||
- #939464: Documented Entity API's module and status properties.
 | 
			
		||||
- #939092: Changed private members to protected in all classes.
 | 
			
		||||
- #936360: Make servers and indexes exportable.
 | 
			
		||||
 | 
			
		||||
Others:
 | 
			
		||||
- #966512: "Time ago" option for Views date fields (+bug fix for missing value).
 | 
			
		||||
- #965318: Lots of notices if entities are missing in Views.
 | 
			
		||||
- #961210: Hide error messages.
 | 
			
		||||
- #963756: Array to string conversion error.
 | 
			
		||||
- #961276: Some random bugs.
 | 
			
		||||
- #961122: Exportability UI fixes.
 | 
			
		||||
- #913858: Fix adding properties that are lists of entities.
 | 
			
		||||
- #961210: Don't hide error messages.
 | 
			
		||||
- #961122: Display configuration status when viewing entities.
 | 
			
		||||
- #889286: EntityAPIController::load() produces WSoD sometimes.
 | 
			
		||||
- #958378 by marvil07: "Clear index" is broken
 | 
			
		||||
- #955892: Typo in search_api_solr.install.
 | 
			
		||||
- #951830: "List of language IDs" context suspicious.
 | 
			
		||||
- #939414: Rename "data-alter callbacks" to "data alterations".
 | 
			
		||||
- #939460: Views integration troubles.
 | 
			
		||||
- #945754: Fix server and index machine name inputs.
 | 
			
		||||
- #943578: Duplicate fields on service creation.
 | 
			
		||||
- #709892: Invoke hook_entity_delete() on entity deletions.
 | 
			
		||||
- #939414: Set fields provided by data-alter callbacks to "indexed" by default.
 | 
			
		||||
- #939414: Provide a default node index upon installation.
 | 
			
		||||
- #939822 by fago: Support fields.
 | 
			
		||||
- #939442: Bad data type defaults [string for fields with options].
 | 
			
		||||
- #939482: Override export() to work with "magic" __get fields.
 | 
			
		||||
- #939442: Bad data type defaults.
 | 
			
		||||
- #939414: Improved descriptions for processors.
 | 
			
		||||
- #939414: Removed the "Call hook" data alter callback.
 | 
			
		||||
- #938982: Not all SearchApiQuery options are passed.
 | 
			
		||||
- #931066 by luke_b: HTTP timeout not set correctly.
 | 
			
		||||
 | 
			
		||||
Search API 1.0, Beta 3 (09/30/2010):
 | 
			
		||||
------------------------------------
 | 
			
		||||
- API mostly stable.
 | 
			
		||||
- Five contrib modules exist:
 | 
			
		||||
  - search_api_db
 | 
			
		||||
  - search_api_solr
 | 
			
		||||
  - search_api_page
 | 
			
		||||
  - search_api_views
 | 
			
		||||
  - search_api_facets
 | 
			
		||||
							
								
								
									
										339
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,339 @@
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 2, June 1991
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 | 
			
		||||
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
 | 
			
		||||
  The licenses for most software are designed to take away your
 | 
			
		||||
freedom to share and change it.  By contrast, the GNU General Public
 | 
			
		||||
License is intended to guarantee your freedom to share and change free
 | 
			
		||||
software--to make sure the software is free for all its users.  This
 | 
			
		||||
General Public License applies to most of the Free Software
 | 
			
		||||
Foundation's software and to any other program whose authors commit to
 | 
			
		||||
using it.  (Some other Free Software Foundation software is covered by
 | 
			
		||||
the GNU Lesser General Public License instead.)  You can apply it to
 | 
			
		||||
your programs, too.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
this service if you wish), that you receive source code or can get it
 | 
			
		||||
if you want it, that you can change the software or use pieces of it
 | 
			
		||||
in new free programs; and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  To protect your rights, we need to make restrictions that forbid
 | 
			
		||||
anyone to deny you these rights or to ask you to surrender the rights.
 | 
			
		||||
These restrictions translate to certain responsibilities for you if you
 | 
			
		||||
distribute copies of the software, or if you modify it.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must give the recipients all the rights that
 | 
			
		||||
you have.  You must make sure that they, too, receive or can get the
 | 
			
		||||
source code.  And you must show them these terms so they know their
 | 
			
		||||
rights.
 | 
			
		||||
 | 
			
		||||
  We protect your rights with two steps: (1) copyright the software, and
 | 
			
		||||
(2) offer you this license which gives you legal permission to copy,
 | 
			
		||||
distribute and/or modify the software.
 | 
			
		||||
 | 
			
		||||
  Also, for each author's protection and ours, we want to make certain
 | 
			
		||||
that everyone understands that there is no warranty for this free
 | 
			
		||||
software.  If the software is modified by someone else and passed on, we
 | 
			
		||||
want its recipients to know that what they have is not the original, so
 | 
			
		||||
that any problems introduced by others will not reflect on the original
 | 
			
		||||
authors' reputations.
 | 
			
		||||
 | 
			
		||||
  Finally, any free program is threatened constantly by software
 | 
			
		||||
patents.  We wish to avoid the danger that redistributors of a free
 | 
			
		||||
program will individually obtain patent licenses, in effect making the
 | 
			
		||||
program proprietary.  To prevent this, we have made it clear that any
 | 
			
		||||
patent must be licensed for everyone's free use or not licensed at all.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 | 
			
		||||
 | 
			
		||||
  0. This License applies to any program or other work which contains
 | 
			
		||||
a notice placed by the copyright holder saying it may be distributed
 | 
			
		||||
under the terms of this General Public License.  The "Program", below,
 | 
			
		||||
refers to any such program or work, and a "work based on the Program"
 | 
			
		||||
means either the Program or any derivative work under copyright law:
 | 
			
		||||
that is to say, a work containing the Program or a portion of it,
 | 
			
		||||
either verbatim or with modifications and/or translated into another
 | 
			
		||||
language.  (Hereinafter, translation is included without limitation in
 | 
			
		||||
the term "modification".)  Each licensee is addressed as "you".
 | 
			
		||||
 | 
			
		||||
Activities other than copying, distribution and modification are not
 | 
			
		||||
covered by this License; they are outside its scope.  The act of
 | 
			
		||||
running the Program is not restricted, and the output from the Program
 | 
			
		||||
is covered only if its contents constitute a work based on the
 | 
			
		||||
Program (independent of having been made by running the Program).
 | 
			
		||||
Whether that is true depends on what the Program does.
 | 
			
		||||
 | 
			
		||||
  1. You may copy and distribute verbatim copies of the Program's
 | 
			
		||||
source code as you receive it, in any medium, provided that you
 | 
			
		||||
conspicuously and appropriately publish on each copy an appropriate
 | 
			
		||||
copyright notice and disclaimer of warranty; keep intact all the
 | 
			
		||||
notices that refer to this License and to the absence of any warranty;
 | 
			
		||||
and give any other recipients of the Program a copy of this License
 | 
			
		||||
along with the Program.
 | 
			
		||||
 | 
			
		||||
You may charge a fee for the physical act of transferring a copy, and
 | 
			
		||||
you may at your option offer warranty protection in exchange for a fee.
 | 
			
		||||
 | 
			
		||||
  2. You may modify your copy or copies of the Program or any portion
 | 
			
		||||
of it, thus forming a work based on the Program, and copy and
 | 
			
		||||
distribute such modifications or work under the terms of Section 1
 | 
			
		||||
above, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) You must cause the modified files to carry prominent notices
 | 
			
		||||
    stating that you changed the files and the date of any change.
 | 
			
		||||
 | 
			
		||||
    b) You must cause any work that you distribute or publish, that in
 | 
			
		||||
    whole or in part contains or is derived from the Program or any
 | 
			
		||||
    part thereof, to be licensed as a whole at no charge to all third
 | 
			
		||||
    parties under the terms of this License.
 | 
			
		||||
 | 
			
		||||
    c) If the modified program normally reads commands interactively
 | 
			
		||||
    when run, you must cause it, when started running for such
 | 
			
		||||
    interactive use in the most ordinary way, to print or display an
 | 
			
		||||
    announcement including an appropriate copyright notice and a
 | 
			
		||||
    notice that there is no warranty (or else, saying that you provide
 | 
			
		||||
    a warranty) and that users may redistribute the program under
 | 
			
		||||
    these conditions, and telling the user how to view a copy of this
 | 
			
		||||
    License.  (Exception: if the Program itself is interactive but
 | 
			
		||||
    does not normally print such an announcement, your work based on
 | 
			
		||||
    the Program is not required to print an announcement.)
 | 
			
		||||
 | 
			
		||||
These requirements apply to the modified work as a whole.  If
 | 
			
		||||
identifiable sections of that work are not derived from the Program,
 | 
			
		||||
and can be reasonably considered independent and separate works in
 | 
			
		||||
themselves, then this License, and its terms, do not apply to those
 | 
			
		||||
sections when you distribute them as separate works.  But when you
 | 
			
		||||
distribute the same sections as part of a whole which is a work based
 | 
			
		||||
on the Program, the distribution of the whole must be on the terms of
 | 
			
		||||
this License, whose permissions for other licensees extend to the
 | 
			
		||||
entire whole, and thus to each and every part regardless of who wrote it.
 | 
			
		||||
 | 
			
		||||
Thus, it is not the intent of this section to claim rights or contest
 | 
			
		||||
your rights to work written entirely by you; rather, the intent is to
 | 
			
		||||
exercise the right to control the distribution of derivative or
 | 
			
		||||
collective works based on the Program.
 | 
			
		||||
 | 
			
		||||
In addition, mere aggregation of another work not based on the Program
 | 
			
		||||
with the Program (or with a work based on the Program) on a volume of
 | 
			
		||||
a storage or distribution medium does not bring the other work under
 | 
			
		||||
the scope of this License.
 | 
			
		||||
 | 
			
		||||
  3. You may copy and distribute the Program (or a work based on it,
 | 
			
		||||
under Section 2) in object code or executable form under the terms of
 | 
			
		||||
Sections 1 and 2 above provided that you also do one of the following:
 | 
			
		||||
 | 
			
		||||
    a) Accompany it with the complete corresponding machine-readable
 | 
			
		||||
    source code, which must be distributed under the terms of Sections
 | 
			
		||||
    1 and 2 above on a medium customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    b) Accompany it with a written offer, valid for at least three
 | 
			
		||||
    years, to give any third party, for a charge no more than your
 | 
			
		||||
    cost of physically performing source distribution, a complete
 | 
			
		||||
    machine-readable copy of the corresponding source code, to be
 | 
			
		||||
    distributed under the terms of Sections 1 and 2 above on a medium
 | 
			
		||||
    customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    c) Accompany it with the information you received as to the offer
 | 
			
		||||
    to distribute corresponding source code.  (This alternative is
 | 
			
		||||
    allowed only for noncommercial distribution and only if you
 | 
			
		||||
    received the program in object code or executable form with such
 | 
			
		||||
    an offer, in accord with Subsection b above.)
 | 
			
		||||
 | 
			
		||||
The source code for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it.  For an executable work, complete source
 | 
			
		||||
code means all the source code for all modules it contains, plus any
 | 
			
		||||
associated interface definition files, plus the scripts used to
 | 
			
		||||
control compilation and installation of the executable.  However, as a
 | 
			
		||||
special exception, the source code distributed need not include
 | 
			
		||||
anything that is normally distributed (in either source or binary
 | 
			
		||||
form) with the major components (compiler, kernel, and so on) of the
 | 
			
		||||
operating system on which the executable runs, unless that component
 | 
			
		||||
itself accompanies the executable.
 | 
			
		||||
 | 
			
		||||
If distribution of executable or object code is made by offering
 | 
			
		||||
access to copy from a designated place, then offering equivalent
 | 
			
		||||
access to copy the source code from the same place counts as
 | 
			
		||||
distribution of the source code, even though third parties are not
 | 
			
		||||
compelled to copy the source along with the object code.
 | 
			
		||||
 | 
			
		||||
  4. You may not copy, modify, sublicense, or distribute the Program
 | 
			
		||||
except as expressly provided under this License.  Any attempt
 | 
			
		||||
otherwise to copy, modify, sublicense or distribute the Program is
 | 
			
		||||
void, and will automatically terminate your rights under this License.
 | 
			
		||||
However, parties who have received copies, or rights, from you under
 | 
			
		||||
this License will not have their licenses terminated so long as such
 | 
			
		||||
parties remain in full compliance.
 | 
			
		||||
 | 
			
		||||
  5. You are not required to accept this License, since you have not
 | 
			
		||||
signed it.  However, nothing else grants you permission to modify or
 | 
			
		||||
distribute the Program or its derivative works.  These actions are
 | 
			
		||||
prohibited by law if you do not accept this License.  Therefore, by
 | 
			
		||||
modifying or distributing the Program (or any work based on the
 | 
			
		||||
Program), you indicate your acceptance of this License to do so, and
 | 
			
		||||
all its terms and conditions for copying, distributing or modifying
 | 
			
		||||
the Program or works based on it.
 | 
			
		||||
 | 
			
		||||
  6. Each time you redistribute the Program (or any work based on the
 | 
			
		||||
Program), the recipient automatically receives a license from the
 | 
			
		||||
original licensor to copy, distribute or modify the Program subject to
 | 
			
		||||
these terms and conditions.  You may not impose any further
 | 
			
		||||
restrictions on the recipients' exercise of the rights granted herein.
 | 
			
		||||
You are not responsible for enforcing compliance by third parties to
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  7. If, as a consequence of a court judgment or allegation of patent
 | 
			
		||||
infringement or for any other reason (not limited to patent issues),
 | 
			
		||||
conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot
 | 
			
		||||
distribute so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you
 | 
			
		||||
may not distribute the Program at all.  For example, if a patent
 | 
			
		||||
license would not permit royalty-free redistribution of the Program by
 | 
			
		||||
all those who receive copies directly or indirectly through you, then
 | 
			
		||||
the only way you could satisfy both it and this License would be to
 | 
			
		||||
refrain entirely from distribution of the Program.
 | 
			
		||||
 | 
			
		||||
If any portion of this section is held invalid or unenforceable under
 | 
			
		||||
any particular circumstance, the balance of the section is intended to
 | 
			
		||||
apply and the section as a whole is intended to apply in other
 | 
			
		||||
circumstances.
 | 
			
		||||
 | 
			
		||||
It is not the purpose of this section to induce you to infringe any
 | 
			
		||||
patents or other property right claims or to contest validity of any
 | 
			
		||||
such claims; this section has the sole purpose of protecting the
 | 
			
		||||
integrity of the free software distribution system, which is
 | 
			
		||||
implemented by public license practices.  Many people have made
 | 
			
		||||
generous contributions to the wide range of software distributed
 | 
			
		||||
through that system in reliance on consistent application of that
 | 
			
		||||
system; it is up to the author/donor to decide if he or she is willing
 | 
			
		||||
to distribute software through any other system and a licensee cannot
 | 
			
		||||
impose that choice.
 | 
			
		||||
 | 
			
		||||
This section is intended to make thoroughly clear what is believed to
 | 
			
		||||
be a consequence of the rest of this License.
 | 
			
		||||
 | 
			
		||||
  8. If the distribution and/or use of the Program is restricted in
 | 
			
		||||
certain countries either by patents or by copyrighted interfaces, the
 | 
			
		||||
original copyright holder who places the Program under this License
 | 
			
		||||
may add an explicit geographical distribution limitation excluding
 | 
			
		||||
those countries, so that distribution is permitted only in or among
 | 
			
		||||
countries not thus excluded.  In such case, this License incorporates
 | 
			
		||||
the limitation as if written in the body of this License.
 | 
			
		||||
 | 
			
		||||
  9. The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the General Public License from time to time.  Such new versions will
 | 
			
		||||
be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
Each version is given a distinguishing version number.  If the Program
 | 
			
		||||
specifies a version number of this License which applies to it and "any
 | 
			
		||||
later version", you have the option of following the terms and conditions
 | 
			
		||||
either of that version or of any later version published by the Free
 | 
			
		||||
Software Foundation.  If the Program does not specify a version number of
 | 
			
		||||
this License, you may choose any version ever published by the Free Software
 | 
			
		||||
Foundation.
 | 
			
		||||
 | 
			
		||||
  10. If you wish to incorporate parts of the Program into other free
 | 
			
		||||
programs whose distribution conditions are different, write to the author
 | 
			
		||||
to ask for permission.  For software which is copyrighted by the Free
 | 
			
		||||
Software Foundation, write to the Free Software Foundation; we sometimes
 | 
			
		||||
make exceptions for this.  Our decision will be guided by the two goals
 | 
			
		||||
of preserving the free status of all derivatives of our free software and
 | 
			
		||||
of promoting the sharing and reuse of software generally.
 | 
			
		||||
 | 
			
		||||
                            NO WARRANTY
 | 
			
		||||
 | 
			
		||||
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 | 
			
		||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 | 
			
		||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 | 
			
		||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 | 
			
		||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 | 
			
		||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 | 
			
		||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 | 
			
		||||
REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 | 
			
		||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 | 
			
		||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 | 
			
		||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 | 
			
		||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 | 
			
		||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 | 
			
		||||
POSSIBILITY OF SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
convey the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software; you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU General Public License as published by
 | 
			
		||||
    the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU General Public License along
 | 
			
		||||
    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
If the program is interactive, make it output a short notice like this
 | 
			
		||||
when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Gnomovision version 69, Copyright (C) year name of author
 | 
			
		||||
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, the commands you use may
 | 
			
		||||
be called something other than `show w' and `show c'; they could even be
 | 
			
		||||
mouse-clicks or menu items--whatever suits your program.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or your
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary.  Here is a sample; alter the names:
 | 
			
		||||
 | 
			
		||||
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 | 
			
		||||
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 | 
			
		||||
 | 
			
		||||
  <signature of Ty Coon>, 1 April 1989
 | 
			
		||||
  Ty Coon, President of Vice
 | 
			
		||||
 | 
			
		||||
This General Public License does not permit incorporating your program into
 | 
			
		||||
proprietary programs.  If your program is a subroutine library, you may
 | 
			
		||||
consider it more useful to permit linking proprietary applications with the
 | 
			
		||||
library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.
 | 
			
		||||
							
								
								
									
										404
									
								
								README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								README.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,404 @@
 | 
			
		||||
Search API
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
This module provides a framework for easily creating searches on any entity
 | 
			
		||||
known to Drupal, using any kind of search engine. For site administrators, it is
 | 
			
		||||
a great alternative to other search solutions, since it already incorporates
 | 
			
		||||
facetting support and the ability to use the Views module for displaying search
 | 
			
		||||
results, filters, etc. Also, with the Apache Solr integration [1], a
 | 
			
		||||
high-performance search engine is available for use with the Search API.
 | 
			
		||||
 | 
			
		||||
If you need help with the module, please post to the project's issue queue [2].
 | 
			
		||||
 | 
			
		||||
[1] http://drupal.org/project/search_api_solr
 | 
			
		||||
[2] http://drupal.org/project/issues/search_api
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Content:
 | 
			
		||||
 - Glossary
 | 
			
		||||
 - Information for users
 | 
			
		||||
 - Information for developers
 | 
			
		||||
 - Included components
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Glossary
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
Terms as used in this module.
 | 
			
		||||
 | 
			
		||||
- Service class:
 | 
			
		||||
  A type of search engine, e.g. using the database, Apache Solr,
 | 
			
		||||
  Sphinx or any other professional or simple indexing mechanism. Takes care of
 | 
			
		||||
  the details of all operations, especially indexing or searching content.
 | 
			
		||||
- Server:
 | 
			
		||||
  One specific place for indexing data, using a set service class. Can
 | 
			
		||||
  e.g. be some tables in a database, a connection to a Solr server or other
 | 
			
		||||
  external services, etc.
 | 
			
		||||
- Index:
 | 
			
		||||
  A configuration object for indexing data of a specific type. What and how data
 | 
			
		||||
  is indexed is determined by its settings. Also keeps track of which items
 | 
			
		||||
  still need to be indexed (or re-indexed, if they were updated). Needs to lie
 | 
			
		||||
  on a server in order to be really used (although configuration is independent
 | 
			
		||||
  of a server).
 | 
			
		||||
- Item type:
 | 
			
		||||
  A type of data which can be indexed (i.e., for which indexes can be created).
 | 
			
		||||
  Most entity types (like Content, User, Taxonomy term, etc.) are available, but
 | 
			
		||||
  possibly also other types provided by contrib modules.
 | 
			
		||||
- Entity:
 | 
			
		||||
  One object of data, usually stored in the database. Might for example
 | 
			
		||||
  be a node, a user or a file.
 | 
			
		||||
- Field:
 | 
			
		||||
  A defined property of an entity, like a node's title or a user's mail address.
 | 
			
		||||
  All fields have defined datatypes. However, for indexing purposes the user
 | 
			
		||||
  might choose to index a property under a different data type than defined.
 | 
			
		||||
- Data type:
 | 
			
		||||
  Determines how a field is indexed. While "Fulltext" fields can be completely
 | 
			
		||||
  searched for keywords, other fields can only be used for filtering. They will
 | 
			
		||||
  also be converted to fit their respective value ranges.
 | 
			
		||||
  How types other than "Fulltext" are handled depends on the service class used.
 | 
			
		||||
  Its documentation should state how the type-selection affect the indexed
 | 
			
		||||
  content. However, service classes will always be able to handle all data
 | 
			
		||||
  types, it is just possible that the type doesn't affect the indexing at all
 | 
			
		||||
  (apart from "Fulltext vs. the rest").
 | 
			
		||||
- Boost:
 | 
			
		||||
  Number determining how important a certain field is, when searching for
 | 
			
		||||
  fulltext keywords. The higher the value is, the more important is the field.
 | 
			
		||||
  E.g., when the node title has a boost of 5.0 and the node body a boost of 1.0,
 | 
			
		||||
  keywords found in the title will increase the score as much as five keywords
 | 
			
		||||
  found in the body. Of course, this has only an effect when the score is used
 | 
			
		||||
  (for sorting or other purposes). It has no effect on other parts of the search
 | 
			
		||||
  result.
 | 
			
		||||
- Data alteration:
 | 
			
		||||
  A component that is used when indexing data. It can add additional fields to
 | 
			
		||||
  the indexed entity or prevent certain entities from being indexed. Fields
 | 
			
		||||
  added by callbacks have to be enabled on the "Fields" page to be of any use,
 | 
			
		||||
  but this is done by default.
 | 
			
		||||
- Processor:
 | 
			
		||||
  An object that is used for preprocessing indexed data as well as search
 | 
			
		||||
  queries, and for postprocessing search results. Usually only work on fulltext
 | 
			
		||||
  fields to control how content is indexed and searched. E.g., processors can be
 | 
			
		||||
  used to make searches case-insensitive, to filter markup out of indexed
 | 
			
		||||
  content, etc.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Information for users
 | 
			
		||||
---------------------
 | 
			
		||||
 | 
			
		||||
IMPORTANT: Access checks
 | 
			
		||||
  In general, the Search API doesn't contain any access checks for search
 | 
			
		||||
  results. It is your responsibility to ensure that only accessible search
 | 
			
		||||
  results are displayed – either by only indexing such items, or by filtering
 | 
			
		||||
  appropriately at search time.
 | 
			
		||||
  For search on general site content (item type "Node"), this is already
 | 
			
		||||
  supported by the Search API. To enable this, go to the index's "Workflow" tab
 | 
			
		||||
  and activate the "Node access" data alteration. This will add the necessary
 | 
			
		||||
  field, "Node access information", to the index (which you have to leave as
 | 
			
		||||
  "indexed"). If both this field and "Published" are set to be indexed, access
 | 
			
		||||
  checks will automatically be executed at search time, showing only those
 | 
			
		||||
  results that a user can view. Some search types (e.g., search views) also
 | 
			
		||||
  provide the option to disable these access checks for individual searches.
 | 
			
		||||
  Please note, however, that these access checks use the indexed data, while
 | 
			
		||||
  usually the current data is displayed to users. Therefore, users might still
 | 
			
		||||
  see inappropriate content as long as items aren't indexed in their latest
 | 
			
		||||
  state. If you can't allow this for your site, please use the index's "Index
 | 
			
		||||
  immediately" feature (explained below) or possibly custom solutions for
 | 
			
		||||
  specific search types, if available.
 | 
			
		||||
 | 
			
		||||
As stated above, you will need at least one other module to use the Search API,
 | 
			
		||||
namely one that defines a service class (e.g. search_api_db ("Database search"),
 | 
			
		||||
provided with this module).
 | 
			
		||||
 | 
			
		||||
- Creating a server
 | 
			
		||||
  (Configuration > Search API > Add server)
 | 
			
		||||
 | 
			
		||||
The most basic thing you have to create is a search server for indexing content.
 | 
			
		||||
Go to Configuration > Search API in the administration pages and select
 | 
			
		||||
"Add server". Name and description are usually only shown to administrators and
 | 
			
		||||
can be used to differentiate between several servers, or to explain a server's
 | 
			
		||||
use to other administrators (for larger sites). Disabling a server makes it
 | 
			
		||||
unusable for indexing and searching and can e.g. be used if the underlying
 | 
			
		||||
search engine is temporarily unavailable.
 | 
			
		||||
The "service class" is the most important option here, since it lets you select
 | 
			
		||||
which backend the search server will use. This cannot be changed after the
 | 
			
		||||
server is created.
 | 
			
		||||
Depending on the selected service class, further, service-specific settings will
 | 
			
		||||
be available. For details on those settings, consult the respective service's
 | 
			
		||||
documentation.
 | 
			
		||||
 | 
			
		||||
- Creating an index
 | 
			
		||||
  (Configuration > Search API > Add index)
 | 
			
		||||
 | 
			
		||||
For adding a search index, choose "Add index" on the Search API administration
 | 
			
		||||
page. Name, description and "enabled" status serve the exact same purpose as
 | 
			
		||||
for servers.
 | 
			
		||||
The most important option in this form is the indexed entity type. Every index
 | 
			
		||||
contains data on only a single type of entities, e.g. nodes, users or taxonomy
 | 
			
		||||
terms. This is therefore the only option that cannot be changed afterwards.
 | 
			
		||||
The server on which the index lies determines where the data will actually be
 | 
			
		||||
indexed. It doesn't affect any other settings of the index and can later be
 | 
			
		||||
changed with the only drawback being that the index' content will have to be
 | 
			
		||||
indexed again. You can also select a server that is at the moment disabled, or
 | 
			
		||||
choose to let the index lie on no server at all, for the time being. Note,
 | 
			
		||||
however, that you can only create enabled indexes on an enabled server. Also,
 | 
			
		||||
disabling a server will disable all indexes that lie on it.
 | 
			
		||||
The "Index items immediately" option specifies that you want items to be
 | 
			
		||||
directly re-indexed after being changed, instead of waiting for the next cron
 | 
			
		||||
run. Use this if it is important that users see no stale data in searches, and
 | 
			
		||||
only when your setup enables relatively fast indexing.
 | 
			
		||||
Lastly, the "Cron batch size" option allows you to set whether items will be
 | 
			
		||||
indexed when cron runs (as long as the index is enabled), and how many items
 | 
			
		||||
will be indexed in a single batch. The best value for this setting depends on
 | 
			
		||||
how time-consuming indexing is for your setup, which in turn depends mostly on
 | 
			
		||||
the server used and the enabled data alterations. You should set it to a number
 | 
			
		||||
of items which can easily be indexed in 10 seconds' time. Items can also be
 | 
			
		||||
indexed manually, or directly when they are changed, so even if this is set to
 | 
			
		||||
0, the index can still be used.
 | 
			
		||||
 | 
			
		||||
- Indexed fields
 | 
			
		||||
  (Configuration > Search API > [Index name] > Fields)
 | 
			
		||||
 | 
			
		||||
Here you can select which of the entities' fields will be indexed, and how.
 | 
			
		||||
Fields added by (enabled) data alterations will be available here, too.
 | 
			
		||||
Without selecting fields to index, the index will be useless and also won't be
 | 
			
		||||
available for searches. Select the "Fulltext" data type for fields which you
 | 
			
		||||
want search for keywords, and other data types when you want to use the field
 | 
			
		||||
for filtering (e.g., as facets). The "Item language" field will always be
 | 
			
		||||
indexed as it contains important information for processors and hooks.
 | 
			
		||||
You can also add fields of related entities here, via the "Add related fields"
 | 
			
		||||
form at the bottom of the page. For instance, you might want to index the
 | 
			
		||||
author's username to the indexed data of a node, and you need to add the "Body"
 | 
			
		||||
entity to the node when you want to index the actual text it contains.
 | 
			
		||||
 | 
			
		||||
- Index workflow
 | 
			
		||||
  (Configuration > Search API > [Index name] > Workflow)
 | 
			
		||||
 | 
			
		||||
This page lets you customize how the created index works, and what metadata will
 | 
			
		||||
be available, by selecting data alterations and processors (see the glossary for
 | 
			
		||||
further explanations).
 | 
			
		||||
Data alterations usually only add one or more fields to the entity and their
 | 
			
		||||
order is mostly irrelevant.
 | 
			
		||||
The order of processors, however, often is important. Read the processors'
 | 
			
		||||
descriptions or consult their documentation for determining how to use them most
 | 
			
		||||
effectively.
 | 
			
		||||
 | 
			
		||||
- Index status
 | 
			
		||||
  (Configuration > Search API > [Index name] > Status)
 | 
			
		||||
 | 
			
		||||
On this page you can view how much of the entities are already indexed and also
 | 
			
		||||
control indexing. With the "Index now" button (displayed only when there are
 | 
			
		||||
still unindexed items) you can directly index a certain number of "dirty" items
 | 
			
		||||
(i.e., items not yet indexed in their current state). Setting "-1" as the number
 | 
			
		||||
will index all of those items, similar to the cron batch size setting.
 | 
			
		||||
When you change settings that could affect indexing, and the index is not
 | 
			
		||||
automatically marked for re-indexing, you can do this manually with the
 | 
			
		||||
"Re-index content" button. All items in the index will be marked as dirty and be
 | 
			
		||||
re-indexed when subsequently indexing items (either manually or via cron runs).
 | 
			
		||||
Until all content is re-indexed, the old data will still show up in searches.
 | 
			
		||||
This is different with the "Clear index" button. All items will be marked as
 | 
			
		||||
dirty and additionally all data will be removed from the index. Therefore,
 | 
			
		||||
searches won't show any results until items are re-indexed, after clearing an
 | 
			
		||||
index. Use this only if completely wrong data has been indexed. It is also done
 | 
			
		||||
automatically when the index scheme or server settings change too drastically to
 | 
			
		||||
keep on using the old data.
 | 
			
		||||
 | 
			
		||||
- Hidden settings
 | 
			
		||||
 | 
			
		||||
search_api_index_worker_callback_runtime:
 | 
			
		||||
  By changing this variable, you can determine the time (in seconds) the Search
 | 
			
		||||
  API will spend indexing (for all indexes combined) in each cron run. The
 | 
			
		||||
  default is 15 seconds.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Information for developers
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
 | NOTE:
 | 
			
		||||
 | For modules providing new entities: In order for your entities to become
 | 
			
		||||
 | searchable with the Search API, your module will need to implement
 | 
			
		||||
 | hook_entity_property_info() in addition to the normal hook_entity_info().
 | 
			
		||||
 | hook_entity_property_info() is documented in the entity module.
 | 
			
		||||
 | For making certain non-entities searchable, see "Item type" below.
 | 
			
		||||
 | For custom field types to be available for indexing, provide a
 | 
			
		||||
 | "property_type" key in hook_field_info(), and optionally a callback at the
 | 
			
		||||
 | "property_callbacks" key.
 | 
			
		||||
 | Both processes are explained in [1].
 | 
			
		||||
 |
 | 
			
		||||
 | [1] http://drupal.org/node/1021466
 | 
			
		||||
 | 
			
		||||
Apart from improving the module itself, developers can extend search
 | 
			
		||||
capabilities provided by the Search API by providing implementations for one (or
 | 
			
		||||
several) of the following classes. Detailed documentation on the methods that
 | 
			
		||||
need to be implemented are always available as doc comments in the respective
 | 
			
		||||
interface definition (all found in their respective files in the includes/
 | 
			
		||||
directory). The details for hooks can be looked up in the search_api.api.php
 | 
			
		||||
file.
 | 
			
		||||
For all interfaces there are handy base classes which can (but don't need to) be
 | 
			
		||||
used to ease custom implementations, since they provide sensible generic
 | 
			
		||||
implementations for many methods. They, too, should be documented well enough
 | 
			
		||||
with doc comments for a developer to find the right methods to override or
 | 
			
		||||
implement.
 | 
			
		||||
 | 
			
		||||
- Service class
 | 
			
		||||
  Interface: SearchApiServiceInterface
 | 
			
		||||
  Base class: SearchApiAbstractService
 | 
			
		||||
  Hook: hook_search_api_service_info()
 | 
			
		||||
 | 
			
		||||
The service classes are the heart of the API, since they allow data to be
 | 
			
		||||
indexed on different search servers. Since these are quite some work to get
 | 
			
		||||
right, you should probably make sure a service class for a specific search
 | 
			
		||||
engine doesn't exist already before programming it yourself.
 | 
			
		||||
When your module supplies a service class, please make sure to provide
 | 
			
		||||
documentation (at least a README.txt) that clearly states the datatypes it
 | 
			
		||||
supports (and in what manner), how a direct query (a query where the keys are
 | 
			
		||||
a single string, instead of an array) is parsed and possible limitations of the
 | 
			
		||||
service class.
 | 
			
		||||
The central methods here are the indexItems() and the search() methods, which
 | 
			
		||||
always have to be overridden manually. The configurationForm() method allows
 | 
			
		||||
services to provide custom settings for the user.
 | 
			
		||||
See the SearchApiDbService class for an example implementation.
 | 
			
		||||
 | 
			
		||||
- Query class
 | 
			
		||||
  Interface: SearchApiQueryInterface
 | 
			
		||||
  Base class: SearchApiQuery
 | 
			
		||||
 | 
			
		||||
You can also override the query class' behaviour for your service class. You
 | 
			
		||||
can, for example, change key parsing behaviour, add additional parse modes
 | 
			
		||||
specific to your service, or override methods so the information is stored more
 | 
			
		||||
suitable for your service.
 | 
			
		||||
For the query class to become available (other than through manual creation),
 | 
			
		||||
you need a custom service class where you override the query() method to return
 | 
			
		||||
an instance of your query class.
 | 
			
		||||
 | 
			
		||||
- Item type
 | 
			
		||||
  Interface: SearchApiDataSourceControllerInterface
 | 
			
		||||
  Base class: SearchApiAbstractDataSourceController
 | 
			
		||||
  Hook: hook_search_api_item_type_info()
 | 
			
		||||
 | 
			
		||||
If you want to index some data which is not defined as an entity, you can
 | 
			
		||||
specify it as a new item type here. For defining a new item type, you have to
 | 
			
		||||
create a data source controller for the type and track new, changed and deleted
 | 
			
		||||
items of the type by calling the search_api_track_item_*() functions.
 | 
			
		||||
An instance of the data source controller class will then be used by indexes
 | 
			
		||||
when handling items of your newly-defined type.
 | 
			
		||||
 | 
			
		||||
If you want to make external data that is indexed on some search server
 | 
			
		||||
available to the Search API, there is a handy base class for your data source
 | 
			
		||||
controller (SearchApiExternalDataSourceController in
 | 
			
		||||
includes/datasource_external.inc) which you can extend. For a minimal use case,
 | 
			
		||||
you will then only have to define the available fields that can be retrieved by
 | 
			
		||||
the server.
 | 
			
		||||
 | 
			
		||||
- Data type
 | 
			
		||||
  Hook: hook_search_api_data_type_info()
 | 
			
		||||
 | 
			
		||||
You can specify new data types for indexing fields. These new types can then be
 | 
			
		||||
selected on indexes' „Fields“ tabs. You just have to implement the hook,
 | 
			
		||||
returning some information on your data type, and specify in your module's
 | 
			
		||||
documentation the format of your data type and how it should be used.
 | 
			
		||||
 | 
			
		||||
For a custom data type to have an effect, in most cases the server's service
 | 
			
		||||
class has to support that data type. A service class can advertize its support
 | 
			
		||||
of a data type by declaring support for the "search_api_data_type_TYPE" feature
 | 
			
		||||
in its supportsFeature() method. If this support isn't declared, a fallback data
 | 
			
		||||
type is automatically used instead of the custom one.
 | 
			
		||||
 | 
			
		||||
If a field is indexed with a custom data type, its entry in the index's options
 | 
			
		||||
array will have the selected type in "real_type", while "type" contains the
 | 
			
		||||
fallback type (which is always one of the default data types, as returned by
 | 
			
		||||
search_api_default_field_types().
 | 
			
		||||
 | 
			
		||||
- Data-alter callbacks
 | 
			
		||||
  Interface: SearchApiAlterCallbackInterface
 | 
			
		||||
  Base class: SearchApiAbstractAlterCallback
 | 
			
		||||
  Hook: hook_search_api_alter_callback_info()
 | 
			
		||||
 | 
			
		||||
Data alter callbacks can be used to change the field data of indexed items, or
 | 
			
		||||
to prevent certain items from being indexed. They are only used when indexing,
 | 
			
		||||
or when selecting the fields to index. For adding additional information to
 | 
			
		||||
search results, you have to use a processor.
 | 
			
		||||
Data-alter callbacks are called "data alterations" in the UI.
 | 
			
		||||
 | 
			
		||||
- Processors
 | 
			
		||||
  Interface: SearchApiProcessorInterface
 | 
			
		||||
  Base class: SearchApiAbstractProcessor
 | 
			
		||||
  Hook: hook_search_api_processor_info()
 | 
			
		||||
 | 
			
		||||
Processors are used for altering the data when indexing or searching. The exact
 | 
			
		||||
specifications are available in the interface's doc comments. Just note that the
 | 
			
		||||
processor description should clearly state assumptions or restrictions on input
 | 
			
		||||
types (e.g. only tokenized text), item language, etc. and explain concisely what
 | 
			
		||||
effect it will have on searches.
 | 
			
		||||
See the processors in includes/processor.inc for examples.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Included components
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
- Service classes
 | 
			
		||||
 | 
			
		||||
  * Database search
 | 
			
		||||
    A search server implementation that uses the normal database for indexing
 | 
			
		||||
    data. It isn't very fast and the results might also be less accurate than
 | 
			
		||||
    with third-party solutions like Solr, but it's very easy to set up and good
 | 
			
		||||
    for smaller applications or testing.
 | 
			
		||||
    See contrib/search_api_db/README.txt for details.
 | 
			
		||||
 | 
			
		||||
- Data alterations
 | 
			
		||||
 | 
			
		||||
  * URL field
 | 
			
		||||
    Provides a field with the URL for displaying the entity.
 | 
			
		||||
  * Aggregated fields
 | 
			
		||||
    Offers the ability to add additional fields to the entity, containing the
 | 
			
		||||
    data from one or more other fields. Use this, e.g., to have a single field
 | 
			
		||||
    containing all data that should be searchable, or to make the text from a
 | 
			
		||||
    string field, like a taxonomy term, also fulltext-searchable.
 | 
			
		||||
    The type of aggregation can be selected from a set of values: you can, e.g.,
 | 
			
		||||
    collect the text data of all contained fields, or add them up, count their
 | 
			
		||||
    values, etc.
 | 
			
		||||
  * Bundle filter
 | 
			
		||||
    Enables the admin to prevent entities from being indexed based on their
 | 
			
		||||
    bundle (content type for nodes, vocabulary for taxonomy terms, etc.).
 | 
			
		||||
  * Complete entity view
 | 
			
		||||
    Adds a field containing the whole HTML content of the entity as it is viewed
 | 
			
		||||
    on the site. The view mode used can be selected.
 | 
			
		||||
    Note, however, that this might not work for entities of all types. All core
 | 
			
		||||
    entities except files are supported, though.
 | 
			
		||||
  * Index hierarchy
 | 
			
		||||
    Allows to index a hierarchical field along with all its parents. Most
 | 
			
		||||
    importantly, this can be used to index taxonomy term references along with
 | 
			
		||||
    all parent terms. This way, when an item, e.g., has the term "New York", it
 | 
			
		||||
    will also be matched when filtering for "USA" or "North America".
 | 
			
		||||
 | 
			
		||||
- Processors
 | 
			
		||||
 | 
			
		||||
  * Ignore case
 | 
			
		||||
    Makes all fulltext searches (and, optionally, also filters on string values)
 | 
			
		||||
    case-insensitive. Some servers might do this automatically, for others this
 | 
			
		||||
    should probably always be activated.
 | 
			
		||||
  * HTML filter
 | 
			
		||||
    Strips HTML tags from fulltext fields and decodes HTML entities. If you are
 | 
			
		||||
    indexing HTML content (like node bodies) and the search server doesn't
 | 
			
		||||
    handle HTML on its own, this should be activated to avoid indexing HTML
 | 
			
		||||
    tags, as well as to give e.g. terms appearing in a heading a higher boost.
 | 
			
		||||
  * Tokenizer
 | 
			
		||||
    This processor allows you to specify how indexed fulltext content is split
 | 
			
		||||
    into seperate tokens – which characters are ignored and which treated as
 | 
			
		||||
    white-space that seperates words.
 | 
			
		||||
  * Stopwords
 | 
			
		||||
    Enables the admin to specify a stopwords file, the words contained in which
 | 
			
		||||
    will be filtered out of the text data indexed. This can be used to exclude
 | 
			
		||||
    too common words from indexing, for servers not supporting this natively.
 | 
			
		||||
 | 
			
		||||
- Additional modules
 | 
			
		||||
 | 
			
		||||
  * Search pages
 | 
			
		||||
    This module lets you create simple search pages for indexes.
 | 
			
		||||
  * Search views
 | 
			
		||||
    This integrates the Search API with the Views module [1], enabling the user
 | 
			
		||||
    to create views which display search results from any Search API index.
 | 
			
		||||
  * Search facets
 | 
			
		||||
    For service classes supporting this feature (e.g. Solr search), this module
 | 
			
		||||
    automatically provides configurable facet blocks on pages that execute
 | 
			
		||||
    a search query.
 | 
			
		||||
 | 
			
		||||
[1] http://drupal.org/project/views
 | 
			
		||||
							
								
								
									
										125
									
								
								contrib/search_api_facetapi/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								contrib/search_api_facetapi/README.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
Search facets
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
This module allows you to create facetted searches for any search executed via
 | 
			
		||||
the Search API, no matter if executed by a search page, a view or any other
 | 
			
		||||
module. The only thing you'll need is a search service class that supports the
 | 
			
		||||
"search_api_facets" feature. Currently, the "Database search" and "Solr search"
 | 
			
		||||
modules supports this.
 | 
			
		||||
 | 
			
		||||
This module is built on the Facet API [1], which is needed for this module to
 | 
			
		||||
work.
 | 
			
		||||
 | 
			
		||||
[1] http://drupal.org/project/facetapi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Information for site builders
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
For creating a facetted search, you first need a search. Create or find some
 | 
			
		||||
page displaying Search API search results, either via a search page, a view or
 | 
			
		||||
by any other means. Now go to the configuration page for the index on which
 | 
			
		||||
this search is executed.
 | 
			
		||||
If the index lies on a server supporting facets (and if this module is enabled),
 | 
			
		||||
you'll notice a "Facets" tab. Click it and it will take you to the index' facet
 | 
			
		||||
configuration page. You'll see a table containing all indexed fields and options
 | 
			
		||||
for enabling and configuring facets for them.
 | 
			
		||||
For a detailed explanation of the available options, please refer to the Facet
 | 
			
		||||
API documentation.
 | 
			
		||||
 | 
			
		||||
- Creating facets via the URL
 | 
			
		||||
 | 
			
		||||
Facets can be added to a search (for which facets are activated) by passing
 | 
			
		||||
appropriate GET parameters in the URL. Assuming you have an indexed field with
 | 
			
		||||
the machine name "field_price", you can filter on it in the following ways:
 | 
			
		||||
 | 
			
		||||
- Filter for a specific value. For finding only results that have a price of
 | 
			
		||||
  exactly 100, pass the following $options to url() or l():
 | 
			
		||||
 | 
			
		||||
  $options['query']['f'][] = 'field_price:100';
 | 
			
		||||
 | 
			
		||||
  Or manually append the following GET parameter to a URL:
 | 
			
		||||
 | 
			
		||||
  ?f[0]=field_price:100
 | 
			
		||||
 | 
			
		||||
- Search for values in a specified range. The following example will only return
 | 
			
		||||
  items that have a price greater than or equal to 100 and lower than 500.
 | 
			
		||||
 | 
			
		||||
  Code: $options['query']['f'][] = 'field_price:[100 TO 500]';
 | 
			
		||||
  URL:  ?f[0]=field_price%3A%5B100%20TO%20500%5D
 | 
			
		||||
 | 
			
		||||
- Search for values above a value. The next example will find results which have
 | 
			
		||||
  a price greater than or equal to 100. The asterisk (*) stands for "unlimited",
 | 
			
		||||
  meaning that there is no upper limit. Filtering for values lower than a
 | 
			
		||||
  certain value works equivalently.
 | 
			
		||||
 | 
			
		||||
  Code: $options['query']['f'][] = 'field_price:[100 TO *]';
 | 
			
		||||
  URL:  ?f[0]=field_price%3A%5B100%20TO%20%2A%5D
 | 
			
		||||
 | 
			
		||||
- Search for missing values. This example will filter out all items which have
 | 
			
		||||
  any value at all in the price field, and will therefore only list items on
 | 
			
		||||
  which this field was omitted. (This naturally only makes sense for fields
 | 
			
		||||
  that aren't required.)
 | 
			
		||||
 | 
			
		||||
  Code: $options['query']['f'][] = 'field_price:!';
 | 
			
		||||
  URL:  ?f[0]=field_price%3A%21
 | 
			
		||||
 | 
			
		||||
- Search for present values. The following example will only return items which
 | 
			
		||||
  have the price field set (regardless of the actual value). You can see that it
 | 
			
		||||
  is actually just a range filter with unlimited lower and upper bound.
 | 
			
		||||
 | 
			
		||||
  Code: $options['query']['f'][] = 'field_price:[* TO *]';
 | 
			
		||||
  URL:  ?f[0]=field_price%3A%5B%2A%20TO%20%2A%5D
 | 
			
		||||
 | 
			
		||||
Note: When filtering a field whose machine name contains a colon (e.g.,
 | 
			
		||||
"author:roles"), you'll have to additionally URL-encode the field name in these
 | 
			
		||||
filter values:
 | 
			
		||||
  Code: $options['query']['f'][] = rawurlencode('author:roles') . ':100';
 | 
			
		||||
  URL:  ?f[0]=author%253Aroles%3A100
 | 
			
		||||
 | 
			
		||||
- Issues
 | 
			
		||||
 | 
			
		||||
If you find any bugs or shortcomings while using this module, please file an
 | 
			
		||||
issue in the project's issue queue [1], using the "Facets" component.
 | 
			
		||||
 | 
			
		||||
[1] http://drupal.org/project/issues/search_api
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Information for developers
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Features
 | 
			
		||||
 | 
			
		||||
If you are the developer of a SearchApiServiceInterface implementation and want
 | 
			
		||||
to support facets with your service class, too, you'll have to support the
 | 
			
		||||
"search_api_facets" feature. You can find details about the necessary additions
 | 
			
		||||
to your class in the example_servive.php file. In short, you'll just, when
 | 
			
		||||
executing a query, have to return facet terms and counts according to the
 | 
			
		||||
query's "search_api_facets" option, if present.
 | 
			
		||||
In order for the module to be able to tell that your server supports facets,
 | 
			
		||||
you will also have to change your service's supportsFeature() method to
 | 
			
		||||
something like the following:
 | 
			
		||||
  public function supportsFeature($feature) {
 | 
			
		||||
    return $feature == 'search_api_facets';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
There is also a second feature defined by this module, namely
 | 
			
		||||
"search_api_facets_operator_or", for supporting "OR" facets. The requirements
 | 
			
		||||
for this feature are also explained in the example_servive.php file.
 | 
			
		||||
 | 
			
		||||
- Query option
 | 
			
		||||
 | 
			
		||||
The facets created follow the "search_api_base_path" option on the search query.
 | 
			
		||||
If set, this path will be used as the base path from which facet links will be
 | 
			
		||||
created. This can be used to show facets on pages without searches – e.g., as a
 | 
			
		||||
landing page.
 | 
			
		||||
 | 
			
		||||
- Hidden variable
 | 
			
		||||
 | 
			
		||||
The module uses one hidden variable, "search_api_facets_search_ids", to keep
 | 
			
		||||
track of the search IDs of searches executed for a given index. It is only
 | 
			
		||||
updated when a facet is displayed for the respective search, so isn't really a
 | 
			
		||||
reliable measure for this.
 | 
			
		||||
In any case, if you e.g. did some test searches and now don't want them to show
 | 
			
		||||
up in the block configuration forever after, just clear the variable:
 | 
			
		||||
  variable_del("search_api_facets_search_ids")
 | 
			
		||||
							
								
								
									
										209
									
								
								contrib/search_api_facetapi/example_service.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								contrib/search_api_facetapi/example_service.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Example implementation for a service class which supports facets.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Example class explaining how facets can be supported by a service class.
 | 
			
		||||
 *
 | 
			
		||||
 * This class defines the "search_api_facets" and
 | 
			
		||||
 * "search_api_facets_operator_or" features. Read the method documentation and
 | 
			
		||||
 * inline comments in search() to learn how they can be supported by a service
 | 
			
		||||
 * class.
 | 
			
		||||
 */
 | 
			
		||||
abstract class SearchApiFacetapiExampleService extends SearchApiAbstractService {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether this service class implementation supports a given
 | 
			
		||||
   * feature. Features are optional extensions to Search API functionality and
 | 
			
		||||
   * usually defined and used by third-party modules.
 | 
			
		||||
   *
 | 
			
		||||
   * If the service class supports facets, it should return TRUE if called with
 | 
			
		||||
   * the feature name "search_api_facets". If it also supports "OR" facets, it
 | 
			
		||||
   * should also return TRUE if called with "search_api_facets_operator_or".
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $feature
 | 
			
		||||
   *   The name of the optional feature.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if this service knows and supports the specified feature. FALSE
 | 
			
		||||
   *   otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsFeature($feature) {
 | 
			
		||||
    $supported = array(
 | 
			
		||||
      'search_api_facets' => TRUE,
 | 
			
		||||
      'search_api_facets_operator_or' => TRUE,
 | 
			
		||||
    );
 | 
			
		||||
    return isset($supported[$feature]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes a search on the server represented by this object.
 | 
			
		||||
   *
 | 
			
		||||
   * If the service class supports facets, it should check for an additional
 | 
			
		||||
   * option on the query object:
 | 
			
		||||
   * - search_api_facets: An array of facets to return along with the results
 | 
			
		||||
   *   for this query. The array is keyed by an arbitrary string which should
 | 
			
		||||
   *   serve as the facet's unique identifier for this search. The values are
 | 
			
		||||
   *   arrays with the following keys:
 | 
			
		||||
   *   - field: The field to construct facets for.
 | 
			
		||||
   *   - limit: The maximum number of facet terms to return. 0 or an empty
 | 
			
		||||
   *     value means no limit.
 | 
			
		||||
   *   - min_count: The minimum number of results a facet value has to have in
 | 
			
		||||
   *     order to be returned.
 | 
			
		||||
   *   - missing: If TRUE, a facet for all items with no value for this field
 | 
			
		||||
   *     should be returned (if it conforms to limit and min_count).
 | 
			
		||||
   *   - operator: (optional) If the service supports "OR" facets and this key
 | 
			
		||||
   *     contains the string "or", the returned facets should be "OR" facets. If
 | 
			
		||||
   *     the server doesn't support "OR" facets, this key can be ignored.
 | 
			
		||||
   *
 | 
			
		||||
   * The basic principle of facets is explained quite well in the
 | 
			
		||||
   * @link http://en.wikipedia.org/wiki/Faceted_search Wikipedia article on
 | 
			
		||||
   * "Faceted search" @endlink. Basically, you should return for each field
 | 
			
		||||
   * filter values which would yield some results when used with the search.
 | 
			
		||||
   * E.g., if you return for a field $field the term $term with $count results,
 | 
			
		||||
   * the given $query along with
 | 
			
		||||
   *   $query->condition($field, $term)
 | 
			
		||||
   * should yield exactly (or about) $count results.
 | 
			
		||||
   *
 | 
			
		||||
   * For "OR" facets, all existing filters on the facetted field should be
 | 
			
		||||
   * ignored for computing the facets.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $query
 | 
			
		||||
   *   The SearchApiQueryInterface object to execute.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the search results, as required by
 | 
			
		||||
   *   SearchApiQueryInterface::execute().
 | 
			
		||||
   *   In addition, if the "search_api_facets" option is present on the query,
 | 
			
		||||
   *   the results should contain an array of facets in the "search_api_facets"
 | 
			
		||||
   *   key, as specified by the option. The facets array should be keyed by the
 | 
			
		||||
   *   facets' unique identifiers, and contain a numeric array of facet terms,
 | 
			
		||||
   *   sorted descending by result count. A term is represented by an array with
 | 
			
		||||
   *   the following keys:
 | 
			
		||||
   *   - count: Number of results for this term.
 | 
			
		||||
   *   - filter: The filter to apply when selecting this facet term. A filter is
 | 
			
		||||
   *     a string of one of the following forms:
 | 
			
		||||
   *     - "VALUE": Filter by the literal value VALUE (always include the
 | 
			
		||||
   *       quotes, not only for strings).
 | 
			
		||||
   *     - [VALUE1 VALUE2]: Filter for a value between VALUE1 and VALUE2. Use
 | 
			
		||||
   *       parantheses for excluding the border values and square brackets for
 | 
			
		||||
   *       including them. An asterisk (*) can be used as a wildcard. E.g.,
 | 
			
		||||
   *       (* 0) or [* 0) would be a filter for all negative values.
 | 
			
		||||
   *     - !: Filter for items without a value for this field (i.e., the
 | 
			
		||||
   *       "missing" facet).
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If an error prevented the search from completing.
 | 
			
		||||
   */
 | 
			
		||||
  public function search(SearchApiQueryInterface $query) {
 | 
			
		||||
    // We assume here that we have an AI search which understands English
 | 
			
		||||
    // commands.
 | 
			
		||||
 | 
			
		||||
    // First, create the normal search query, without facets.
 | 
			
		||||
    $search = new SuperCoolAiSearch($query->getIndex());
 | 
			
		||||
    $search->cmd('create basic search for the following query', $query);
 | 
			
		||||
    $ret = $search->cmd('return search results in Search API format');
 | 
			
		||||
 | 
			
		||||
    // Then, let's see if we should return any facets.
 | 
			
		||||
    if ($facets = $query->getOption('search_api_facets')) {
 | 
			
		||||
      // For the facets, we need all results, not only those in the specified
 | 
			
		||||
      // range.
 | 
			
		||||
      $results = $search->cmd('return unlimited search results as a set');
 | 
			
		||||
      foreach ($facets as $id => $facet) {
 | 
			
		||||
        $field = $facet['field'];
 | 
			
		||||
        $limit = empty($facet['limit']) ? 'all' : $facet['limit'];
 | 
			
		||||
        $min_count = $facet['min_count'];
 | 
			
		||||
        $missing = $facet['missing'];
 | 
			
		||||
        $or = isset($facet['operator']) && $facet['operator'] == 'or';
 | 
			
		||||
 | 
			
		||||
        // If this is an "OR" facet, existing filters on the field should be
 | 
			
		||||
        // ignored for computing the facets.
 | 
			
		||||
        // You can ignore this if your service class doesn't support the
 | 
			
		||||
        // "search_api_facets_operator_or" feature.
 | 
			
		||||
        if ($or) {
 | 
			
		||||
          // We have to execute another query (in the case of this hypothetical
 | 
			
		||||
          // search backend, at least) to get the right result set to facet.
 | 
			
		||||
          $tmp_search = new SuperCoolAiSearch($query->getIndex());
 | 
			
		||||
          $tmp_search->cmd('create basic search for the following query', $query);
 | 
			
		||||
          $tmp_search->cmd("remove all conditions for field $field");
 | 
			
		||||
          $tmp_results = $tmp_search->cmd('return unlimited search results as a set');
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Otherwise, we can just use the normal results.
 | 
			
		||||
          $tmp_results = $results;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $filters = array();
 | 
			
		||||
        if ($search->cmd("$field is a date or numeric field")) {
 | 
			
		||||
          // For date, integer or float fields, range facets are more useful.
 | 
			
		||||
          $ranges = $search->cmd("list $limit ranges of field $field in the following set", $tmp_results);
 | 
			
		||||
          foreach ($ranges as $range) {
 | 
			
		||||
            if ($range->getCount() >= $min_count) {
 | 
			
		||||
              // Get the lower and upper bound of the range. * means unlimited.
 | 
			
		||||
              $lower = $range->getLowerBound();
 | 
			
		||||
              $lower = ($lower == SuperCoolAiSearch::RANGE_UNLIMITED) ? '*' : $lower;
 | 
			
		||||
              $upper = $range->getUpperBound();
 | 
			
		||||
              $upper = ($upper == SuperCoolAiSearch::RANGE_UNLIMITED) ? '*' : $upper;
 | 
			
		||||
              // Then, see whether the bounds are included in the range. These
 | 
			
		||||
              // can be specified independently for the lower and upper bound.
 | 
			
		||||
              // Parentheses are used for exclusive bounds, square brackets are
 | 
			
		||||
              // used for inclusive bounds.
 | 
			
		||||
              $lowChar = $range->isLowerBoundInclusive() ? '[' : '(';
 | 
			
		||||
              $upChar = $range->isUpperBoundInclusive() ? ']' : ')';
 | 
			
		||||
              // Create the filter, which separates the bounds with a single
 | 
			
		||||
              // space.
 | 
			
		||||
              $filter = "$lowChar$lower $upper$upChar";
 | 
			
		||||
              $filters[$filter] = $range->getCount();
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Otherwise, we use normal single-valued facets.
 | 
			
		||||
          $terms = $search->cmd("list $limit values of field $field in the following set", $tmp_results);
 | 
			
		||||
          foreach ($terms as $term) {
 | 
			
		||||
            if ($term->getCount() >= $min_count) {
 | 
			
		||||
              // For single-valued terms, we just need to wrap them in quotes.
 | 
			
		||||
              $filter = '"' . $term->getValue() . '"';
 | 
			
		||||
              $filters[$filter] = $term->getCount();
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we should also return a "missing" facet, compute that as the
 | 
			
		||||
        // number of results without a value for the facet field.
 | 
			
		||||
        if ($missing) {
 | 
			
		||||
          $count = $search->cmd("return number of results without field $field in the following set", $tmp_results);
 | 
			
		||||
          if ($count >= $min_count) {
 | 
			
		||||
            $filters['!'] = $count;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sort the facets descending by result count.
 | 
			
		||||
        arsort($filters);
 | 
			
		||||
 | 
			
		||||
        // With the "missing" facet, we might have too many facet terms (unless
 | 
			
		||||
        // $limit was empty and is therefore now set to "all"). If this is the
 | 
			
		||||
        // case, remove those with the lowest number of results.
 | 
			
		||||
        while (is_numeric($limit) && count($filters) > $limit) {
 | 
			
		||||
          array_pop($filters);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now add the facet terms to the return value, as specified in the doc
 | 
			
		||||
        // comment for this method.
 | 
			
		||||
        foreach ($filters as $filter => $count) {
 | 
			
		||||
          $ret['search_api_facets'][$id][] = array(
 | 
			
		||||
            'count' => $count,
 | 
			
		||||
            'filter' => $filter,
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Return the results, which now also includes the facet information.
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										242
									
								
								contrib/search_api_facetapi/plugins/facetapi/adapter.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								contrib/search_api_facetapi/plugins/facetapi/adapter.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,242 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Classes used by the Facet API module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Facet API adapter for the Search API module.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiFacetapiAdapter extends FacetapiAdapter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cached value for the current search for this searcher, if any.
 | 
			
		||||
   *
 | 
			
		||||
   * @see getCurrentSearch()
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $current_search;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The active facet fields for the current search.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $fields = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the path to the admin settings for a given realm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $realm_name
 | 
			
		||||
   *   The name of the realm.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   The path to the admin settings.
 | 
			
		||||
   */
 | 
			
		||||
  public function getPath($realm_name) {
 | 
			
		||||
    $base_path = 'admin/config/search/search_api';
 | 
			
		||||
    $index_id = $this->info['instance'];
 | 
			
		||||
    return $base_path . '/index/' . $index_id . '/facets/' . $realm_name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Overrides FacetapiAdapter::getSearchPath().
 | 
			
		||||
   */
 | 
			
		||||
  public function getSearchPath() {
 | 
			
		||||
    $search = $this->getCurrentSearch();
 | 
			
		||||
    if ($search && $search[0]->getOption('search_api_base_path')) {
 | 
			
		||||
      return $search[0]->getOption('search_api_base_path');
 | 
			
		||||
    }
 | 
			
		||||
    return $_GET['q'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows the backend to initialize its query object before adding the facet filters.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $query
 | 
			
		||||
   *   The backend's native object.
 | 
			
		||||
   */
 | 
			
		||||
  public function initActiveFilters($query) {
 | 
			
		||||
    $search_id = $query->getOption('search id');
 | 
			
		||||
    $index_id = $this->info['instance'];
 | 
			
		||||
    $facets = facetapi_get_enabled_facets($this->info['name']);
 | 
			
		||||
    $this->fields = array();
 | 
			
		||||
 | 
			
		||||
    // We statically store the current search per facet so that we can correctly
 | 
			
		||||
    // assign it when building the facets. See the build() method in the query
 | 
			
		||||
    // type plugin classes.
 | 
			
		||||
    $active = &drupal_static('search_api_facetapi_active_facets', array());
 | 
			
		||||
    foreach ($facets as $facet) {
 | 
			
		||||
      $options = $this->getFacet($facet)->getSettings()->settings;
 | 
			
		||||
      // The 'default_true' option is a choice between "show on all but the
 | 
			
		||||
      // selected searches" (TRUE) and "show for only the selected searches".
 | 
			
		||||
      $default_true = isset($options['default_true']) ? $options['default_true'] : TRUE;
 | 
			
		||||
      // The 'facet_search_ids' option is the list of selected searches that
 | 
			
		||||
      // will either be excluded or for which the facet will exclusively be
 | 
			
		||||
      // displayed.
 | 
			
		||||
      $facet_search_ids = isset($options['facet_search_ids']) ? $options['facet_search_ids'] : array();
 | 
			
		||||
 | 
			
		||||
      if (array_search($search_id, $facet_search_ids) === FALSE) {
 | 
			
		||||
        $search_ids = variable_get('search_api_facets_search_ids', array());
 | 
			
		||||
        if (empty($search_ids[$index_id][$search_id])) {
 | 
			
		||||
          // Remember this search ID.
 | 
			
		||||
          $search_ids[$index_id][$search_id] = $search_id;
 | 
			
		||||
          variable_set('search_api_facets_search_ids', $search_ids);
 | 
			
		||||
        }
 | 
			
		||||
        if (!$default_true) {
 | 
			
		||||
          continue; // We are only to show facets for explicitly named search ids.
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($default_true) {
 | 
			
		||||
        continue; // The 'facet_search_ids' in the settings are to be excluded.
 | 
			
		||||
      }
 | 
			
		||||
      $active[$facet['name']] = $search_id;
 | 
			
		||||
      $this->fields[$facet['name']] = array(
 | 
			
		||||
        'field'             => $facet['field'],
 | 
			
		||||
        'limit'             => $options['hard_limit'],
 | 
			
		||||
        'operator'          => $options['operator'],
 | 
			
		||||
        'min_count'         => $options['facet_mincount'],
 | 
			
		||||
        'missing'           => $options['facet_missing'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add the given facet to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function addFacet(array $facet, SearchApiQueryInterface $query) {
 | 
			
		||||
    if (isset($this->fields[$facet['name']])) {
 | 
			
		||||
      $options = &$query->getOptions();
 | 
			
		||||
      $options['search_api_facets'][$facet['name']] = $this->fields[$facet['name']];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a boolean flagging whether $this->_searcher executed a search.
 | 
			
		||||
   */
 | 
			
		||||
  public function searchExecuted() {
 | 
			
		||||
    return (bool) $this->getCurrentSearch();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for getting a current search for this searcher.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The first matching current search, in the form specified by
 | 
			
		||||
   *   search_api_current_search(). Or NULL, if no match was found.
 | 
			
		||||
   */
 | 
			
		||||
  public function getCurrentSearch() {
 | 
			
		||||
    if (!isset($this->current_search)) {
 | 
			
		||||
      $this->current_search = FALSE;
 | 
			
		||||
      $index_id = $this->info['instance'];
 | 
			
		||||
      // There is currently no way to configure the "current search" block to
 | 
			
		||||
      // show on a per-searcher basis as we do with the facets. Therefore we
 | 
			
		||||
      // cannot match it up to the correct "current search".
 | 
			
		||||
      // I suspect that http://drupal.org/node/593658 would help.
 | 
			
		||||
      // For now, just taking the first current search for this index. :-/
 | 
			
		||||
      foreach (search_api_current_search() as $search) {
 | 
			
		||||
        list($query, $results) = $search;
 | 
			
		||||
        if ($query->getIndex()->machine_name == $index_id) {
 | 
			
		||||
          $this->current_search = $search;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->current_search ? $this->current_search : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a boolean flagging whether facets in a realm shoud be displayed.
 | 
			
		||||
   *
 | 
			
		||||
   * Useful, for example, for suppressing sidebar blocks in some cases.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   A boolean flagging whether to display a given realm.
 | 
			
		||||
   */
 | 
			
		||||
  public function suppressOutput($realm_name) {
 | 
			
		||||
    // Not sure under what circumstances the output will need to be suppressed?
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the search keys.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSearchKeys() {
 | 
			
		||||
    $search = $this->getCurrentSearch();
 | 
			
		||||
    $keys = $search[0]->getOriginalKeys();
 | 
			
		||||
    if (is_array($keys)) {
 | 
			
		||||
      // This will happen nearly never when displaying the search keys to the
 | 
			
		||||
      // user, so go with a simple work-around.
 | 
			
		||||
      // If someone complains, we can easily add a method for printing them
 | 
			
		||||
      // properly.
 | 
			
		||||
      $keys = '[' . t('complex query') . ']';
 | 
			
		||||
    }
 | 
			
		||||
    elseif (!$keys) {
 | 
			
		||||
      // If a base path other than the current one is set, we assume that we
 | 
			
		||||
      // shouldn't report on the current search. Highly hack-y, of course.
 | 
			
		||||
      if ($search[0]->getOption('search_api_base_path', $_GET['q']) !== $_GET['q']) {
 | 
			
		||||
        return NULL;
 | 
			
		||||
      }
 | 
			
		||||
      // Work-around since Facet API won't show the "Current search" block
 | 
			
		||||
      // without keys.
 | 
			
		||||
      $keys = '[' . t('all items') . ']';
 | 
			
		||||
    }
 | 
			
		||||
    drupal_alter('search_api_facetapi_keys', $keys, $search[0]);
 | 
			
		||||
    return $keys;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the number of total results found for the current search.
 | 
			
		||||
   */
 | 
			
		||||
  public function getResultCount() {
 | 
			
		||||
    $search = $this->getCurrentSearch();
 | 
			
		||||
    // Each search is an array with the query as the first element and the results
 | 
			
		||||
    // array as the second.
 | 
			
		||||
    if (isset($search[1])) {
 | 
			
		||||
      return $search[1]['result count'];
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows for backend specific overrides to the settings form.
 | 
			
		||||
   */
 | 
			
		||||
  public function settingsForm(&$form, &$form_state) {
 | 
			
		||||
    $facet = $form['#facetapi']['facet'];
 | 
			
		||||
    $realm = $form['#facetapi']['realm'];
 | 
			
		||||
    $facet_settings = $this->getFacet($facet)->getSettings();
 | 
			
		||||
    $options = $facet_settings->settings;
 | 
			
		||||
    $search_ids = variable_get('search_api_facets_search_ids', array());
 | 
			
		||||
    $search_ids = isset($search_ids[$this->info['instance']]) ? $search_ids[$this->info['instance']] : array();
 | 
			
		||||
    if (count($search_ids) > 1) {
 | 
			
		||||
      $form['global']['default_true'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Display for searches'),
 | 
			
		||||
        '#options' => array(
 | 
			
		||||
          TRUE => t('For all except the selected'),
 | 
			
		||||
          FALSE => t('Only for the selected'),
 | 
			
		||||
        ),
 | 
			
		||||
        '#default_value' => isset($options['default_true']) ? $options['default_true'] : TRUE,
 | 
			
		||||
      );
 | 
			
		||||
      $form['global']['facet_search_ids'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Search IDs'),
 | 
			
		||||
        '#options' => $search_ids,
 | 
			
		||||
        '#size' => min(4, count($search_ids)),
 | 
			
		||||
        '#multiple' => TRUE,
 | 
			
		||||
        '#default_value' => isset($options['facet_search_ids']) ? $options['facet_search_ids'] : array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['global']['default_true'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => TRUE,
 | 
			
		||||
      );
 | 
			
		||||
      $form['global']['facet_search_ids'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										196
									
								
								contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Date query type plugin for the Search API adapter.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin for "date" query types.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQueryTypeInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads the include file containing the date API functions.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(FacetapiAdapter $adapter, array $facet) {
 | 
			
		||||
    module_load_include('date.inc', 'facetapi');
 | 
			
		||||
    parent::__construct($adapter, $facet);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the query type associated with the plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The query type.
 | 
			
		||||
   */
 | 
			
		||||
  static public function getType() {
 | 
			
		||||
    return 'date';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds the filter to the query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $query
 | 
			
		||||
   *   An object containing the query in the backend's native API.
 | 
			
		||||
   */
 | 
			
		||||
  public function execute($query) {
 | 
			
		||||
    // Return terms for this facet.
 | 
			
		||||
    $this->adapter->addFacet($this->facet, $query);
 | 
			
		||||
    // Change limit to "unlimited" (-1).
 | 
			
		||||
    $options = &$query->getOptions();
 | 
			
		||||
    if (!empty($options['search_api_facets'][$this->facet['name']])) {
 | 
			
		||||
      $options['search_api_facets'][$this->facet['name']]['limit'] = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($active = $this->adapter->getActiveItems($this->facet)) {
 | 
			
		||||
      $item = end($active);
 | 
			
		||||
      $field = $this->facet['field'];
 | 
			
		||||
      $regex = str_replace(array('^', '$'), '', FACETAPI_REGEX_DATE);
 | 
			
		||||
      $filter = preg_replace_callback($regex, array($this, 'replaceDateString'), $item['value']);
 | 
			
		||||
      $this->addFacetFilter($query, $field, $filter);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Replacement callback for replacing ISO dates with timestamps.
 | 
			
		||||
   */
 | 
			
		||||
  public function replaceDateString($matches) {
 | 
			
		||||
    return strtotime($matches[0]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initializes the facet's build array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The initialized render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function build() {
 | 
			
		||||
    $facet = $this->adapter->getFacet($this->facet);
 | 
			
		||||
    $search_ids = drupal_static('search_api_facetapi_active_facets', array());
 | 
			
		||||
    if (empty($search_ids[$facet['name']]) || !search_api_current_search($search_ids[$facet['name']])) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    $search_id = $search_ids[$facet['name']];
 | 
			
		||||
    $build = array();
 | 
			
		||||
    $search = search_api_current_search($search_id);
 | 
			
		||||
    $results = $search[1];
 | 
			
		||||
    if (!$results['result count']) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    // Gets total number of documents matched in search.
 | 
			
		||||
    $total = $results['result count'];
 | 
			
		||||
 | 
			
		||||
    // Most of the code below is copied from search_facetapi's implementation of
 | 
			
		||||
    // this method.
 | 
			
		||||
 | 
			
		||||
    // Executes query, iterates over results.
 | 
			
		||||
    if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['field']])) {
 | 
			
		||||
      $values = $results['search_api_facets'][$this->facet['field']];
 | 
			
		||||
      foreach ($values as $value) {
 | 
			
		||||
        if ($value['count']) {
 | 
			
		||||
          $filter = $value['filter'];
 | 
			
		||||
          // We only process single values further. The "missing" filter and
 | 
			
		||||
          // range filters will be passed on unchanged.
 | 
			
		||||
          if ($filter == '!') {
 | 
			
		||||
            $build[$filter]['#count'] = $value['count'];
 | 
			
		||||
          }
 | 
			
		||||
          elseif ($filter[0] == '"') {
 | 
			
		||||
            $filter = substr($value['filter'], 1, -1);
 | 
			
		||||
            if ($filter) {
 | 
			
		||||
              $raw_values[$filter] = $value['count'];
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            $filter = substr($value['filter'], 1, -1);
 | 
			
		||||
            $pos = strpos($filter, ' ');
 | 
			
		||||
            if ($pos !== FALSE) {
 | 
			
		||||
              $lower = facetapi_isodate(substr($filter, 0, $pos), FACETAPI_DATE_DAY);
 | 
			
		||||
              $upper = facetapi_isodate(substr($filter, $pos + 1), FACETAPI_DATE_DAY);
 | 
			
		||||
              $filter = '[' . $lower . ' TO ' . $upper . ']';
 | 
			
		||||
            }
 | 
			
		||||
            $build[$filter]['#count'] = $value['count'];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Gets active facets, starts building hierarchy.
 | 
			
		||||
    $parent = $gap = NULL;
 | 
			
		||||
    foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) {
 | 
			
		||||
      // If the item is active, the count is the result set count.
 | 
			
		||||
      $build[$value] = array('#count' => $total);
 | 
			
		||||
 | 
			
		||||
      // Gets next "gap" increment, minute being the lowest we can go.
 | 
			
		||||
      if ($value[0] != '[' || $value[strlen($value) - 1] != ']' || !($pos = strpos($value, ' TO '))) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $start = substr($value, 1, $pos);
 | 
			
		||||
      $end = substr($value, $pos + 4, -1);
 | 
			
		||||
      $date_gap = facetapi_get_date_gap($start, $end);
 | 
			
		||||
      $gap = facetapi_get_next_date_gap($date_gap, FACETAPI_DATE_MINUTE);
 | 
			
		||||
 | 
			
		||||
      // If there is a previous item, there is a parent, uses a reference so the
 | 
			
		||||
      // arrays are populated when they are updated.
 | 
			
		||||
      if (NULL !== $parent) {
 | 
			
		||||
        $build[$parent]['#item_children'][$value] = &$build[$value];
 | 
			
		||||
        $build[$value]['#item_parents'][$parent] = $parent;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Stores the last value iterated over.
 | 
			
		||||
      $parent = $value;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($raw_values)) {
 | 
			
		||||
      return $build;
 | 
			
		||||
    }
 | 
			
		||||
    ksort($raw_values);
 | 
			
		||||
 | 
			
		||||
    // Mind the gap! Calculates gap from min and max timestamps.
 | 
			
		||||
    $timestamps = array_keys($raw_values);
 | 
			
		||||
    if (NULL === $parent) {
 | 
			
		||||
      if (count($raw_values) > 1) {
 | 
			
		||||
        $gap = facetapi_get_timestamp_gap(min($timestamps), max($timestamps));
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $gap = FACETAPI_DATE_HOUR;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Converts all timestamps to dates in ISO 8601 format.
 | 
			
		||||
    $dates = array();
 | 
			
		||||
    foreach ($timestamps as $timestamp) {
 | 
			
		||||
      $dates[$timestamp] = facetapi_isodate($timestamp, $gap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Treat each date as the range start and next date as the range end.
 | 
			
		||||
    $range_end = array();
 | 
			
		||||
    $previous = NULL;
 | 
			
		||||
    foreach (array_unique($dates) as $date) {
 | 
			
		||||
      if (NULL !== $previous) {
 | 
			
		||||
        $range_end[$previous] = facetapi_get_next_date_increment($previous, $gap);
 | 
			
		||||
      }
 | 
			
		||||
      $previous = $date;
 | 
			
		||||
    }
 | 
			
		||||
    $range_end[$previous] = facetapi_get_next_date_increment($previous, $gap);
 | 
			
		||||
 | 
			
		||||
    // Groups dates by the range they belong to, builds the $build array
 | 
			
		||||
    // with the facet counts and formatted range values.
 | 
			
		||||
    foreach ($raw_values as $value => $count) {
 | 
			
		||||
      $new_value = '[' . $dates[$value] . ' TO ' . $range_end[$dates[$value]] . ']';
 | 
			
		||||
      if (!isset($build[$new_value])) {
 | 
			
		||||
        $build[$new_value] = array('#count' => $count);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $build[$new_value]['#count'] += $count;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Adds parent information if not already set.
 | 
			
		||||
      if (NULL !== $parent && $parent != $new_value) {
 | 
			
		||||
        $build[$parent]['#item_children'][$new_value] = &$build[$new_value];
 | 
			
		||||
        $build[$new_value]['#item_parents'][$parent] = $parent;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Term query type plugin for the Apache Solr adapter.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin for "term" query types.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTypeInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the query type associated with the plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The query type.
 | 
			
		||||
   */
 | 
			
		||||
  static public function getType() {
 | 
			
		||||
    return 'term';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds the filter to the query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiQueryInterface $query
 | 
			
		||||
   *   An object containing the query in the backend's native API.
 | 
			
		||||
   */
 | 
			
		||||
  public function execute($query) {
 | 
			
		||||
    // Return terms for this facet.
 | 
			
		||||
    $this->adapter->addFacet($this->facet, $query);
 | 
			
		||||
 | 
			
		||||
    $settings = $this->adapter->getFacet($this->facet)->getSettings();
 | 
			
		||||
    // Adds the operator parameter.
 | 
			
		||||
    $operator = $settings->settings['operator'];
 | 
			
		||||
 | 
			
		||||
    // Add active facet filters.
 | 
			
		||||
    $active = $this->adapter->getActiveItems($this->facet);
 | 
			
		||||
    if (empty($active)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (FACETAPI_OPERATOR_OR == $operator) {
 | 
			
		||||
      // If we're dealing with an OR facet, we need to use a nested filter.
 | 
			
		||||
      $facet_filter = $query->createFilter('OR');
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Otherwise we set the conditions directly on the query.
 | 
			
		||||
      $facet_filter = $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($active as $filter => $filter_array) {
 | 
			
		||||
      $field = $this->facet['field'];
 | 
			
		||||
      $this->addFacetFilter($facet_filter, $field, $filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For OR facets, we now have to add the filter to the query.
 | 
			
		||||
    if (FACETAPI_OPERATOR_OR == $operator) {
 | 
			
		||||
      $query->filter($facet_filter);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for setting a facet filter on a query or query filter object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function addFacetFilter($query_filter, $field, $filter) {
 | 
			
		||||
    // Integer (or other nun-string) filters might mess up some of the following
 | 
			
		||||
    // comparison expressions.
 | 
			
		||||
    $filter = (string) $filter;
 | 
			
		||||
    if ($filter == '!') {
 | 
			
		||||
      $query_filter->condition($field, NULL);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($filter[0] == '[' && $filter[strlen($filter) - 1] == ']' && ($pos = strpos($filter, ' TO '))) {
 | 
			
		||||
      $lower = trim(substr($filter, 1, $pos));
 | 
			
		||||
      $upper = trim(substr($filter, $pos + 4, -1));
 | 
			
		||||
      if ($lower == '*' && $upper == '*') {
 | 
			
		||||
        $query_filter->condition($field, NULL, '<>');
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        if ($lower != '*') {
 | 
			
		||||
          // Iff we have a range with two finite boundaries, we set two
 | 
			
		||||
          // conditions (larger than the lower bound and less than the upper
 | 
			
		||||
          // bound) and therefore have to make sure that we have an AND
 | 
			
		||||
          // conjunction for those.
 | 
			
		||||
          if ($upper != '*' && !($query_filter instanceof SearchApiQueryInterface || $query_filter->getConjunction() === 'AND')) {
 | 
			
		||||
            $original_query_filter = $query_filter;
 | 
			
		||||
            $query_filter = new SearchApiQueryFilter('AND');
 | 
			
		||||
          }
 | 
			
		||||
          $query_filter->condition($field, $lower, '>=');
 | 
			
		||||
        }
 | 
			
		||||
        if ($upper != '*') {
 | 
			
		||||
          $query_filter->condition($field, $upper, '<=');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $query_filter->condition($field, $filter);
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($original_query_filter)) {
 | 
			
		||||
      $original_query_filter->filter($query_filter);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initializes the facet's build array.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The initialized render array.
 | 
			
		||||
   */
 | 
			
		||||
  public function build() {
 | 
			
		||||
    $facet = $this->adapter->getFacet($this->facet);
 | 
			
		||||
    // The current search per facet is stored in a static variable (during
 | 
			
		||||
    // initActiveFilters) so that we can retrieve it here and get the correct
 | 
			
		||||
    // current search for this facet.
 | 
			
		||||
    $search_ids = drupal_static('search_api_facetapi_active_facets', array());
 | 
			
		||||
    if (empty($search_ids[$facet['name']]) || !search_api_current_search($search_ids[$facet['name']])) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    $search_id = $search_ids[$facet['name']];
 | 
			
		||||
    $search = search_api_current_search($search_id);
 | 
			
		||||
    $build = array();
 | 
			
		||||
    $results = $search[1];
 | 
			
		||||
    if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['field']])) {
 | 
			
		||||
      $values = $results['search_api_facets'][$this->facet['field']];
 | 
			
		||||
      foreach ($values as $value) {
 | 
			
		||||
        $filter = $value['filter'];
 | 
			
		||||
        // As Facet API isn't really suited for our native facet filter
 | 
			
		||||
        // representations, convert the format here. (The missing facet can
 | 
			
		||||
        // stay the same.)
 | 
			
		||||
        if ($filter[0] == '"') {
 | 
			
		||||
          $filter = substr($filter, 1, -1);
 | 
			
		||||
        }
 | 
			
		||||
        elseif ($filter != '!') {
 | 
			
		||||
          // This is a range filter.
 | 
			
		||||
          $filter = substr($filter, 1, -1);
 | 
			
		||||
          $pos = strpos($filter, ' ');
 | 
			
		||||
          if ($pos !== FALSE) {
 | 
			
		||||
            $filter = '[' . substr($filter, 0, $pos) . ' TO ' . substr($filter, $pos + 1) . ']';
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $build[$filter] = array(
 | 
			
		||||
          '#count' => $value['count'],
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $build;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								contrib/search_api_facetapi/search_api_facetapi.api.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								contrib/search_api_facetapi/search_api_facetapi.api.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Hooks provided by the Search facets module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @addtogroup hooks
 | 
			
		||||
 * @{
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Lets modules alter the search keys that are returned to FacetAPI and used
 | 
			
		||||
 * in the current search block and breadcrumb trail.
 | 
			
		||||
 *
 | 
			
		||||
 * @param string $keys
 | 
			
		||||
 *   The string representing the user's current search query.
 | 
			
		||||
 * @param SearchApiQuery $query
 | 
			
		||||
 *   The SearchApiQuery object for the current search.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_facetapi_keys_alter(&$keys, $query) {
 | 
			
		||||
  if ($keys == '[' . t('all items') . ']') {
 | 
			
		||||
    // Change $keys to something else, perhaps based on filters in the query
 | 
			
		||||
    // object.
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @} End of "addtogroup hooks".
 | 
			
		||||
 */
 | 
			
		||||
							
								
								
									
										17
									
								
								contrib/search_api_facetapi/search_api_facetapi.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								contrib/search_api_facetapi/search_api_facetapi.info
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
name = Search facets
 | 
			
		||||
description = "Integrate the Search API with the Facet API to provide facetted searches."
 | 
			
		||||
dependencies[] = search_api
 | 
			
		||||
dependencies[] = facetapi
 | 
			
		||||
core = 7.x
 | 
			
		||||
package = Search
 | 
			
		||||
 | 
			
		||||
files[] = plugins/facetapi/adapter.inc
 | 
			
		||||
files[] = plugins/facetapi/query_type_term.inc
 | 
			
		||||
files[] = plugins/facetapi/query_type_date.inc
 | 
			
		||||
 | 
			
		||||
; Information added by drupal.org packaging script on 2013-01-09
 | 
			
		||||
version = "7.x-1.4"
 | 
			
		||||
core = "7.x"
 | 
			
		||||
project = "search_api"
 | 
			
		||||
datestamp = "1357726719"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								contrib/search_api_facetapi/search_api_facetapi.install
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								contrib/search_api_facetapi/search_api_facetapi.install
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Install, update and uninstall functions for the Search facets module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_uninstall().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_uninstall() {
 | 
			
		||||
  variable_del('search_api_facets_search_ids');
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										381
									
								
								contrib/search_api_facetapi/search_api_facetapi.module
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								contrib/search_api_facetapi/search_api_facetapi.module
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,381 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Integrates the Search API with the Facet API.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_menu().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_menu() {
 | 
			
		||||
  // We need to handle our own menu paths for facets because we need a facet
 | 
			
		||||
  // configuration page per index.
 | 
			
		||||
  $first = TRUE;
 | 
			
		||||
  foreach (facetapi_get_realm_info() as $realm_name => $realm) {
 | 
			
		||||
    if ($first) {
 | 
			
		||||
      $first = FALSE;
 | 
			
		||||
      $items['admin/config/search/search_api/index/%search_api_index/facets'] = array(
 | 
			
		||||
        'title'            => 'Facets',
 | 
			
		||||
        'page callback'    => 'search_api_facetapi_settings',
 | 
			
		||||
        'page arguments'   =>  array($realm_name, 5),
 | 
			
		||||
        'weight'           => -1,
 | 
			
		||||
        'access arguments' => array('administer search_api'),
 | 
			
		||||
        'type'             => MENU_LOCAL_TASK,
 | 
			
		||||
        'context'          => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
 | 
			
		||||
      );
 | 
			
		||||
      $items['admin/config/search/search_api/index/%search_api_index/facets/' . $realm_name] = array(
 | 
			
		||||
        'title'            => $realm['label'],
 | 
			
		||||
        'type'             => MENU_DEFAULT_LOCAL_TASK,
 | 
			
		||||
        'weight'           => $realm['weight'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $items['admin/config/search/search_api/index/%search_api_index/facets/' . $realm_name] = array(
 | 
			
		||||
        'title'            => $realm['label'],
 | 
			
		||||
        'page callback'    => 'search_api_facetapi_settings',
 | 
			
		||||
        'page arguments'   => array($realm_name, 5),
 | 
			
		||||
        'access arguments' => array('administer search_api'),
 | 
			
		||||
        'type'             => MENU_LOCAL_TASK,
 | 
			
		||||
        'context'          => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
 | 
			
		||||
        'weight'           => $realm['weight'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return $items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_facetapi_searcher_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_facetapi_searcher_info() {
 | 
			
		||||
  $info = array();
 | 
			
		||||
  $indexes = search_api_index_load_multiple(FALSE);
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    if ($index->enabled && $index->server()->supportsFeature('search_api_facets')) {
 | 
			
		||||
      $searcher_name = 'search_api@' . $index->machine_name;
 | 
			
		||||
      $info[$searcher_name] = array(
 | 
			
		||||
        'label' => t('Search service: @name', array('@name' => $index->name)),
 | 
			
		||||
        'adapter' => 'search_api',
 | 
			
		||||
        'instance' => $index->machine_name,
 | 
			
		||||
        'types' => array($index->item_type),
 | 
			
		||||
        'path' => '',
 | 
			
		||||
        'supports facet missing' => TRUE,
 | 
			
		||||
        'supports facet mincount' => TRUE,
 | 
			
		||||
        'include default facets' => FALSE,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return $info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_facetapi_facet_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_facetapi_facet_info(array $searcher_info) {
 | 
			
		||||
  $facet_info = array();
 | 
			
		||||
  if ('search_api' == $searcher_info['adapter']) {
 | 
			
		||||
    $index = search_api_index_load($searcher_info['instance']);
 | 
			
		||||
    if (!empty($index->options['fields'])) {
 | 
			
		||||
      $wrapper = $index->entityWrapper();
 | 
			
		||||
      $bundle_key = NULL;
 | 
			
		||||
      if (($entity_info = entity_get_info($index->item_type)) && !empty($entity_info['bundle keys']['bundle'])) {
 | 
			
		||||
        $bundle_key = $entity_info['bundle keys']['bundle'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Some type-specific settings. Allowing to set some additional callbacks
 | 
			
		||||
      // (and other settings) in the map options allows for easier overriding by
 | 
			
		||||
      // other modules.
 | 
			
		||||
      $type_settings = array(
 | 
			
		||||
        'taxonomy_term' => array(
 | 
			
		||||
          'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy',
 | 
			
		||||
        ),
 | 
			
		||||
        'date' => array(
 | 
			
		||||
          'query type' => 'date',
 | 
			
		||||
          'map options' => array(
 | 
			
		||||
            'map callback' => 'facetapi_map_date',
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      // Iterate through the indexed fields to set the facetapi settings for
 | 
			
		||||
      // each one.
 | 
			
		||||
      foreach ($index->getFields() as $key => $field) {
 | 
			
		||||
        $field['key'] = $key;
 | 
			
		||||
        // Determine which, if any, of the field type-specific options will be
 | 
			
		||||
        // used for this field.
 | 
			
		||||
        $type = isset($field['entity_type']) ? $field['entity_type'] : $field['type'];
 | 
			
		||||
        $type_settings += array($type => array());
 | 
			
		||||
 | 
			
		||||
        $facet_info[$key] = $type_settings[$type] + array(
 | 
			
		||||
          'label' => $field['name'],
 | 
			
		||||
          'description' => t('Filter by @type.', array('@type' => $field['name'])),
 | 
			
		||||
          'allowed operators' => array(
 | 
			
		||||
            FACETAPI_OPERATOR_AND => TRUE,
 | 
			
		||||
            FACETAPI_OPERATOR_OR => $index->server()->supportsFeature('search_api_facets_operator_or'),
 | 
			
		||||
          ),
 | 
			
		||||
          'dependency plugins' => array('role'),
 | 
			
		||||
          'facet missing allowed' => TRUE,
 | 
			
		||||
          'facet mincount allowed' => TRUE,
 | 
			
		||||
          'map callback' => 'search_api_facetapi_facet_map_callback',
 | 
			
		||||
          'map options' => array(),
 | 
			
		||||
          'field type' => $type,
 | 
			
		||||
        );
 | 
			
		||||
        if ($type === 'date') {
 | 
			
		||||
          $facet_info[$key]['description'] .= ' ' . t('(Caution: This may perform very poorly for large result sets.)');
 | 
			
		||||
        }
 | 
			
		||||
        $facet_info[$key]['map options'] += array(
 | 
			
		||||
          'field' => $field,
 | 
			
		||||
          'index id' => $index->machine_name,
 | 
			
		||||
          'value callback' => '_search_api_facetapi_facet_create_label',
 | 
			
		||||
        );
 | 
			
		||||
        // Find out whether this property is a Field API field.
 | 
			
		||||
        if (strpos($key, ':') === FALSE) {
 | 
			
		||||
          if (isset($wrapper->$key)) {
 | 
			
		||||
            $property_info = $wrapper->$key->info();
 | 
			
		||||
            if (!empty($property_info['field'])) {
 | 
			
		||||
              $facet_info[$key]['field api name'] = $key;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add bundle information, if applicable.
 | 
			
		||||
        if ($bundle_key) {
 | 
			
		||||
          if ($key === $bundle_key) {
 | 
			
		||||
            // Set entity type this field contains bundle information for.
 | 
			
		||||
            $facet_info[$key]['field api bundles'][] = $index->item_type;
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            // Add "bundle" as possible dependency plugin.
 | 
			
		||||
            $facet_info[$key]['dependency plugins'][] = 'bundle';
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return $facet_info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_facetapi_adapters().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_facetapi_adapters() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'search_api' => array(
 | 
			
		||||
      'handler' => array(
 | 
			
		||||
        'class' => 'SearchApiFacetapiAdapter',
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_facetapi_query_types().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_facetapi_query_types() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'search_api_term' => array(
 | 
			
		||||
      'handler' => array(
 | 
			
		||||
        'class' => 'SearchApiFacetapiTerm',
 | 
			
		||||
        'adapter' => 'search_api',
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'search_api_date' => array(
 | 
			
		||||
      'handler' => array(
 | 
			
		||||
        'class' => 'SearchApiFacetapiDate',
 | 
			
		||||
        'adapter' => 'search_api',
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_search_api_query_alter().
 | 
			
		||||
 *
 | 
			
		||||
 * Adds Facet API support to the query.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_search_api_query_alter($query) {
 | 
			
		||||
  $index = $query->getIndex();
 | 
			
		||||
  if ($index->server()->supportsFeature('search_api_facets')) {
 | 
			
		||||
    // This is the main point of communication between the facet system and the
 | 
			
		||||
    // search back-end - it makes the query respond to active facets.
 | 
			
		||||
    $searcher = 'search_api@' . $index->machine_name;
 | 
			
		||||
    $adapter = facetapi_adapter_load($searcher);
 | 
			
		||||
    if ($adapter) {
 | 
			
		||||
      $adapter->addActiveFilters($query);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Menu callback for the facet settings page.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_settings($realm_name, SearchApiIndex $index) {
 | 
			
		||||
  if (!$index->enabled) {
 | 
			
		||||
    return array('#markup' => t('Since this index is at the moment disabled, no facets can be activated.'));
 | 
			
		||||
  }
 | 
			
		||||
  if (!$index->server()->supportsFeature('search_api_facets')) {
 | 
			
		||||
    return array('#markup' => t('This index uses a server that does not support facet functionality.'));
 | 
			
		||||
  }
 | 
			
		||||
  $searcher_name = 'search_api@' . $index->machine_name;
 | 
			
		||||
  module_load_include('inc', 'facetapi', 'facetapi.admin');
 | 
			
		||||
  return drupal_get_form('facetapi_realm_settings_form', $searcher_name, $realm_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map callback for all search_api facet fields.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $values
 | 
			
		||||
 *   The values to map.
 | 
			
		||||
 * @param array $options
 | 
			
		||||
 *   An associative array containing:
 | 
			
		||||
 *   - field: Field information, as stored in the index, but with an additional
 | 
			
		||||
 *     "key" property set to the field's internal name.
 | 
			
		||||
 *   - index id: The machine name of the index for this facet.
 | 
			
		||||
 *   - map callback: (optional) A callback that will be called at the beginning,
 | 
			
		||||
 *     which allows initial mapping of filters. Only values not mapped by that
 | 
			
		||||
 *     callback will be processed by this method.
 | 
			
		||||
 *   - value callback: A callback used to map single values and the limits of
 | 
			
		||||
 *     ranges. The signature is the same as for this function, but all values
 | 
			
		||||
 *     will be single values.
 | 
			
		||||
 *   - missing label: (optional) The label used for the "missing" facet.
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An array mapping raw filter values to their labels.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_facet_map_callback(array $values, array $options = array()) {
 | 
			
		||||
  $map = array();
 | 
			
		||||
  // See if we have an additional map callback.
 | 
			
		||||
  if (isset($options['map callback']) && is_callable($options['map callback'])) {
 | 
			
		||||
    $map = call_user_func($options['map callback'], $values, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Then look at all unmapped values and save information for them.
 | 
			
		||||
  $mappable_values = array();
 | 
			
		||||
  $ranges = array();
 | 
			
		||||
  foreach ($values as $value) {
 | 
			
		||||
    $value = (string) $value;
 | 
			
		||||
    if (isset($map[$value])) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if ($value == '!') {
 | 
			
		||||
      // The "missing" filter is usually always the same, but we allow an easy
 | 
			
		||||
      // override via the "missing label" map option.
 | 
			
		||||
      $map['!'] = isset($options['missing label']) ? $options['missing label'] : '(' . t('none') . ')';
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    $length = strlen($value);
 | 
			
		||||
    if ($length > 5 && $value[0] == '[' && $value[$length - 1] == ']' && ($pos = strpos($value, ' TO '))) {
 | 
			
		||||
      // This is a range filter.
 | 
			
		||||
      $lower = trim(substr($value, 1, $pos));
 | 
			
		||||
      $upper = trim(substr($value, $pos + 4, -1));
 | 
			
		||||
      if ($lower != '*') {
 | 
			
		||||
        $mappable_values[$lower] = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      if ($upper != '*') {
 | 
			
		||||
        $mappable_values[$upper] = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      $ranges[$value] = array(
 | 
			
		||||
        'lower' => $lower,
 | 
			
		||||
        'upper' => $upper,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // A normal, single-value filter.
 | 
			
		||||
      $mappable_values[$value] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ($mappable_values) {
 | 
			
		||||
    $map += call_user_func($options['value callback'], array_keys($mappable_values), $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  foreach ($ranges as $value => $range) {
 | 
			
		||||
    $lower = isset($map[$range['lower']]) ? $map[$range['lower']] : $range['lower'];
 | 
			
		||||
    $upper = isset($map[$range['upper']]) ? $map[$range['upper']] : $range['upper'];
 | 
			
		||||
    if ($lower == '*' && $upper == '*') {
 | 
			
		||||
      $map[$value] =  t('any');
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($lower == '*') {
 | 
			
		||||
      $map[$value] = "< $upper";
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($upper == '*') {
 | 
			
		||||
      $map[$value] = "> $lower";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $map[$value] = "$lower – $upper";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return $map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a human-readable label for single facet filter values.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_facetapi_facet_create_label(array $values, array $options) {
 | 
			
		||||
  $field = $options['field'];
 | 
			
		||||
  // For entities, we can simply use the entity labels.
 | 
			
		||||
  if (isset($field['entity_type'])) {
 | 
			
		||||
    $type = $field['entity_type'];
 | 
			
		||||
    $entities = entity_load($type, $values);
 | 
			
		||||
    $map = array();
 | 
			
		||||
    foreach ($entities as $id => $entity) {
 | 
			
		||||
      $label = entity_label($type, $entity);
 | 
			
		||||
      if ($label) {
 | 
			
		||||
        $map[$id] = $label;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $map;
 | 
			
		||||
  }
 | 
			
		||||
  // Then, we check whether there is an options list for the field.
 | 
			
		||||
  $index = search_api_index_load($options['index id']);
 | 
			
		||||
  $wrapper = $index->entityWrapper();
 | 
			
		||||
  foreach (explode(':', $field['key']) as $part) {
 | 
			
		||||
    if (!isset($wrapper->$part)) {
 | 
			
		||||
      $wrapper = NULL;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    $wrapper = $wrapper->$part;
 | 
			
		||||
    while (($info = $wrapper->info()) && search_api_is_list_type($info['type'])) {
 | 
			
		||||
      $wrapper = $wrapper[0];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if ($wrapper && ($options = $wrapper->optionsList('view'))) {
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
  // As a "last resort" we try to create a label based on the field type.
 | 
			
		||||
  $map = array();
 | 
			
		||||
  foreach ($values as $value) {
 | 
			
		||||
    switch ($field['type']) {
 | 
			
		||||
      case 'boolean':
 | 
			
		||||
        $map[$value] = $value ? t('true') : t('false');
 | 
			
		||||
        break;
 | 
			
		||||
      case 'date':
 | 
			
		||||
        $v = is_numeric($value) ? $value : strtotime($value);
 | 
			
		||||
        $map[$value] = format_date($v, 'short');
 | 
			
		||||
        break;
 | 
			
		||||
      case 'duration':
 | 
			
		||||
        $map[$value] = format_interval($value);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return $map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_form_FORM_ID_alter().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_form_search_api_admin_index_fields_alter(&$form, &$form_state) {
 | 
			
		||||
  $form['#submit'][] = 'search_api_facetapi_search_api_admin_index_fields_submit';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form submission handler for search_api_admin_index_fields().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_facetapi_search_api_admin_index_fields_submit($form, &$form_state) {
 | 
			
		||||
  // Clears this searcher's cached facet definitions.
 | 
			
		||||
  $cid = 'facetapi:facet_info:search_api@' . $form_state['index']->machine_name . ':';
 | 
			
		||||
  cache_clear_all($cid, 'cache', TRUE);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								contrib/search_api_views/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								contrib/search_api_views/README.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
Search API Views integration
 | 
			
		||||
----------------------------
 | 
			
		||||
 | 
			
		||||
This module integrates the Search API with the popular Views module [1],
 | 
			
		||||
allowing users to create views with filters, arguments, sorts and fields based
 | 
			
		||||
on any search index.
 | 
			
		||||
 | 
			
		||||
[1] http://drupal.org/project/views
 | 
			
		||||
 | 
			
		||||
"More like this" feature
 | 
			
		||||
------------------------
 | 
			
		||||
This module defines the "More like this" feature (feature key: "search_api_mlt")
 | 
			
		||||
that search service classes can implement. With a server supporting this, you
 | 
			
		||||
can use the „More like this“ contextual filter to display a list of items
 | 
			
		||||
related to a given item (usually, nodes similar to the node currently viewed).
 | 
			
		||||
 | 
			
		||||
For developers:
 | 
			
		||||
A service class that wants to support this feature has to check for a
 | 
			
		||||
"search_api_mlt" option in the search() method. When present, it will be an
 | 
			
		||||
array containing two keys:
 | 
			
		||||
- id: The entity ID of the item to which related items should be searched.
 | 
			
		||||
- fields: An array of indexed fields to use for testing the similarity of items.
 | 
			
		||||
When these are present, the normal keywords should be ignored and the related
 | 
			
		||||
items be returned as results instead. Sorting, filtering and range restriction
 | 
			
		||||
should all work normally.
 | 
			
		||||
 | 
			
		||||
"Facets block" display
 | 
			
		||||
----------------------
 | 
			
		||||
Most features should be clear to users of Views. However, the module also
 | 
			
		||||
provides a new display type, "Facets block", that might need some explanation.
 | 
			
		||||
This display type is only available, if the „Search facets“ module is also
 | 
			
		||||
enabled.
 | 
			
		||||
 | 
			
		||||
The basic use of the block is to provide a list of links to the most popular
 | 
			
		||||
filter terms (i.e., the ones with the most results) for a certain category. For
 | 
			
		||||
example, you could provide a block listing the most popular authors, or taxonomy
 | 
			
		||||
terms, linking to searches for those, to provide some kind of landing page.
 | 
			
		||||
 | 
			
		||||
Please note that, due to limitations in Views, this display mode is shown for
 | 
			
		||||
views of all base tables, even though it only works for views based on Search
 | 
			
		||||
API indexes. For views of other base tables, this will just print an error
 | 
			
		||||
message.
 | 
			
		||||
The display will also always ignore the view's "Style" setting, selected fields
 | 
			
		||||
and sorts, etc.
 | 
			
		||||
 | 
			
		||||
To use the display, specify the base path of the search you want to link to
 | 
			
		||||
(this enables you to also link to searches that aren't based on Views) and the
 | 
			
		||||
facet field to use (any indexed field can be used here, there needn't be a facet
 | 
			
		||||
defined for it). You'll then have the block available in the blocks
 | 
			
		||||
administration and can enable and move it at leisure.
 | 
			
		||||
Note, however, that the facet in question has to be enabled for the search page
 | 
			
		||||
linked to for the filter to have an effect.
 | 
			
		||||
 | 
			
		||||
Since the block will trigger a search on pages where it is set to appear, you
 | 
			
		||||
can also enable additional „normal“ facet blocks for that search, via the
 | 
			
		||||
„Facets“ tab for the index. They will automatically also point to the same
 | 
			
		||||
search that you specified for the display. The Search ID of the „Facets blocks“
 | 
			
		||||
display can easily be recognized by the "-facet_block" suffix.
 | 
			
		||||
If you want to use only the normal facets and not display anything at all in
 | 
			
		||||
the Views block, just activate the display's „Hide block“ option.
 | 
			
		||||
 | 
			
		||||
Note: If you want to display the block not only on a few pages, you should in
 | 
			
		||||
any case take care that it isn't displayed on the search page, since that might
 | 
			
		||||
confuse users.
 | 
			
		||||
 | 
			
		||||
FAQ: Why „*Indexed* Node“?
 | 
			
		||||
--------------------------
 | 
			
		||||
The group name used for the search result itself (in fields, filters, etc.) is
 | 
			
		||||
prefixed with „Indexed“ in order to be distinguishable from fields on referenced
 | 
			
		||||
nodes (or other entities). The data displayed normally still comes from the
 | 
			
		||||
entity, not from the search index.
 | 
			
		||||
							
								
								
									
										262
									
								
								contrib/search_api_views/includes/display_facet_block.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								contrib/search_api_views/includes/display_facet_block.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Display plugin for displaying the search facets in a block.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin class for displaying search facets in a block.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
 | 
			
		||||
  public function displays_exposed() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
  public function uses_exposed() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
 | 
			
		||||
    $options['linked_path'] = array('default' => '');
 | 
			
		||||
    $options['facet_field'] = '';
 | 
			
		||||
    $options['hide_block'] = FALSE;
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch ($form_state['section']) {
 | 
			
		||||
      case 'linked_path':
 | 
			
		||||
        $form['#title'] .= t('Search page path');
 | 
			
		||||
        $form['linked_path'] = array(
 | 
			
		||||
          '#type' => 'textfield',
 | 
			
		||||
          '#description' => t('The menu path to which search facets will link. Leave empty to use the current path.'),
 | 
			
		||||
          '#default_value' => $this->get_option('linked_path'),
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
      case 'facet_field':
 | 
			
		||||
        $form['facet_field'] = array(
 | 
			
		||||
          '#type' => 'select',
 | 
			
		||||
          '#title' => t('Facet field'),
 | 
			
		||||
          '#options' => $this->getFieldOptions(),
 | 
			
		||||
          '#default_value' => $this->get_option('facet_field'),
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
      case 'use_more':
 | 
			
		||||
        $form['use_more']['#description'] = t('This will add a more link to the bottom of this view, which will link to the base path for the facet links.');
 | 
			
		||||
        $form['use_more_always'] = array(
 | 
			
		||||
          '#type' => 'value',
 | 
			
		||||
          '#value' => $this->get_option('use_more_always'),
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
      case 'hide_block':
 | 
			
		||||
        $form['hide_block'] = array(
 | 
			
		||||
          '#type' => 'checkbox',
 | 
			
		||||
          '#title' => t('Hide block'),
 | 
			
		||||
          '#description' => t('Hide this block, but still execute the search. ' .
 | 
			
		||||
              'Can be used to show native Facet API facet blocks linking to the search page specified above.'),
 | 
			
		||||
          '#default_value' => $this->get_option('hide_block'),
 | 
			
		||||
        );
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function options_validate(&$form, &$form_state) {
 | 
			
		||||
    if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
 | 
			
		||||
      form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function options_submit(&$form, &$form_state) {
 | 
			
		||||
    parent::options_submit($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    switch ($form_state['section']) {
 | 
			
		||||
      case 'linked_path':
 | 
			
		||||
        $this->set_option('linked_path', $form_state['values']['linked_path']);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'facet_field':
 | 
			
		||||
        $this->set_option('facet_field', $form_state['values']['facet_field']);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'hide_block':
 | 
			
		||||
        $this->set_option('hide_block', $form_state['values']['hide_block']);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function options_summary(&$categories, &$options) {
 | 
			
		||||
    parent::options_summary($categories, $options);
 | 
			
		||||
 | 
			
		||||
    $options['linked_path'] = array(
 | 
			
		||||
      'category' => 'block',
 | 
			
		||||
      'title' => t('Search page path'),
 | 
			
		||||
      'value' => $this->get_option('linked_path') ? $this->get_option('linked_path') : t('Use current path'),
 | 
			
		||||
    );
 | 
			
		||||
    $field_options = $this->getFieldOptions();
 | 
			
		||||
    $options['facet_field'] = array(
 | 
			
		||||
      'category' => 'block',
 | 
			
		||||
      'title' => t('Facet field'),
 | 
			
		||||
      'value' => $this->get_option('facet_field') ? $field_options[$this->get_option('facet_field')] : t('None'),
 | 
			
		||||
    );
 | 
			
		||||
    $options['hide_block'] = array(
 | 
			
		||||
      'category' => 'block',
 | 
			
		||||
      'title' => t('Hide block'),
 | 
			
		||||
      'value' => $this->get_option('hide_block') ? t('Yes') : t('No'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected $field_options = NULL;
 | 
			
		||||
 | 
			
		||||
  protected function getFieldOptions() {
 | 
			
		||||
    if (!isset($this->field_options)) {
 | 
			
		||||
      $index_id = substr($this->view->base_table, 17);
 | 
			
		||||
      if (!($index_id && ($index = search_api_index_load($index_id)))) {
 | 
			
		||||
        $table = views_fetch_data($this->view->base_table);
 | 
			
		||||
        $table = empty($table['table']['base']['title']) ? $this->view->base_table : $table['table']['base']['title'];
 | 
			
		||||
        throw new SearchApiException(t('The "Facets block" display cannot be used with a view for @basetable. ' .
 | 
			
		||||
            'Please only use this display with base tables representing search indexes.',
 | 
			
		||||
            array('@basetable' => $table)));
 | 
			
		||||
      }
 | 
			
		||||
      $this->field_options = array();
 | 
			
		||||
      if (!empty($index->options['fields'])) {
 | 
			
		||||
        foreach ($index->getFields() as $key => $field) {
 | 
			
		||||
          $this->field_options[$key] = $field['name'];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->field_options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render the 'more' link
 | 
			
		||||
   */
 | 
			
		||||
  public function render_more_link() {
 | 
			
		||||
    if ($this->use_more()) {
 | 
			
		||||
      $path = $this->get_option('linked_path');
 | 
			
		||||
      $theme = views_theme_functions('views_more', $this->view, $this->display);
 | 
			
		||||
      $path = check_url(url($path, array()));
 | 
			
		||||
 | 
			
		||||
      return array(
 | 
			
		||||
        '#theme' => $theme,
 | 
			
		||||
        '#more_url' => $path,
 | 
			
		||||
        '#link_text' => check_plain($this->use_more_text()),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function execute() {
 | 
			
		||||
    if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
 | 
			
		||||
      form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $facet_field = $this->get_option('facet_field');
 | 
			
		||||
    if (!$facet_field) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $base_path = $this->get_option('linked_path');
 | 
			
		||||
    if (!$base_path) {
 | 
			
		||||
      $base_path = $_GET['q'];
 | 
			
		||||
    }
 | 
			
		||||
    $this->view->build();
 | 
			
		||||
    $limit = empty($this->view->query->pager->options['items_per_page']) ? 10 : $this->view->query->pager->options['items_per_page'];
 | 
			
		||||
    $query_options = &$this->view->query->getOptions();
 | 
			
		||||
    if (!$this->get_option('hide_block')) {
 | 
			
		||||
      // If we hide the block, we don't need this extra facet.
 | 
			
		||||
      $query_options['search_api_facets']['search_api_views_facets_block'] = array(
 | 
			
		||||
        'field' => $facet_field,
 | 
			
		||||
        'limit' => $limit,
 | 
			
		||||
        'missing' => FALSE,
 | 
			
		||||
        'min_count' => 1,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $query_options['search id'] = 'search_api_views:' . $this->view->name . '-facets_block';
 | 
			
		||||
    $query_options['search_api_base_path'] = $base_path;
 | 
			
		||||
    $this->view->query->range(0, 0);
 | 
			
		||||
 | 
			
		||||
    $this->view->execute();
 | 
			
		||||
 | 
			
		||||
    if ($this->get_option('hide_block')) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $results = $this->view->query->getSearchApiResults();
 | 
			
		||||
 | 
			
		||||
    if (empty($results['search_api_facets']['search_api_views_facets_block'])) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $terms = $results['search_api_facets']['search_api_views_facets_block'];
 | 
			
		||||
 | 
			
		||||
    $filters = array();
 | 
			
		||||
    foreach ($terms as $term) {
 | 
			
		||||
      $filter = $term['filter'];
 | 
			
		||||
      if ($filter[0] == '"') {
 | 
			
		||||
        $filter = substr($filter, 1, -1);
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($filter != '!') {
 | 
			
		||||
        // This is a range filter.
 | 
			
		||||
        $filter = substr($filter, 1, -1);
 | 
			
		||||
        $pos = strpos($filter, ' ');
 | 
			
		||||
        if ($pos !== FALSE) {
 | 
			
		||||
          $filter = '[' . substr($filter, 0, $pos) . ' TO ' . substr($filter, $pos + 1) . ']';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $filters[$term['filter']] = $filter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $index = $this->view->query->getIndex();
 | 
			
		||||
    $options['field'] = $index->options['fields'][$facet_field];
 | 
			
		||||
    $options['field']['key'] = $facet_field;
 | 
			
		||||
    $options['index id'] = $index->machine_name;
 | 
			
		||||
    $options['value callback'] = '_search_api_facetapi_facet_create_label';
 | 
			
		||||
    $map = search_api_facetapi_facet_map_callback($filters, $options);
 | 
			
		||||
 | 
			
		||||
    $facets = array();
 | 
			
		||||
    $prefix = rawurlencode($facet_field) . ':';
 | 
			
		||||
    foreach ($terms as $term) {
 | 
			
		||||
      $name = $filter = $filters[$term['filter']];
 | 
			
		||||
      if (isset($map[$filter])) {
 | 
			
		||||
        $name = $map[$filter];
 | 
			
		||||
      }
 | 
			
		||||
      $query['f'][0] = $prefix . $filter;
 | 
			
		||||
 | 
			
		||||
      // Initializes variables passed to theme hook.
 | 
			
		||||
      $variables = array(
 | 
			
		||||
        'text' => $name,
 | 
			
		||||
        'path' => $base_path,
 | 
			
		||||
        'count' => $term['count'],
 | 
			
		||||
        'options' => array(
 | 
			
		||||
          'attributes' => array('class' => 'facetapi-inactive'),
 | 
			
		||||
          'html' => FALSE,
 | 
			
		||||
          'query' => $query,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      // Themes the link, adds row to facets.
 | 
			
		||||
      $facets[] = array(
 | 
			
		||||
        'class' => array('leaf'),
 | 
			
		||||
        'data' => theme('facetapi_link_inactive', $variables),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!$facets) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $info['content']['facets'] = array(
 | 
			
		||||
      '#theme'  => 'item_list',
 | 
			
		||||
      '#items'  => $facets,
 | 
			
		||||
    );
 | 
			
		||||
    $info['content']['more'] = $this->render_more_link();
 | 
			
		||||
    $info['subject'] = filter_xss_admin($this->view->get_title());
 | 
			
		||||
    return $info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								contrib/search_api_views/includes/handler_argument.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								contrib/search_api_views/includes/handler_argument.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views argument handler class for handling all non-fulltext types.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerArgument extends views_handler_argument {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated views query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiViewsQuery
 | 
			
		||||
   */
 | 
			
		||||
  public $query;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determine if the argument can generate a breadcrumb
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   */
 | 
			
		||||
  // @todo Change and implement set_breadcrumb()?
 | 
			
		||||
  public function uses_breadcrumb() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a list of default behaviors for this argument if the argument
 | 
			
		||||
   * is not present.
 | 
			
		||||
   *
 | 
			
		||||
   * Override this method to provide additional (or fewer) default behaviors.
 | 
			
		||||
   */
 | 
			
		||||
  public function default_actions($which = NULL) {
 | 
			
		||||
    $defaults = array(
 | 
			
		||||
      'ignore' => array(
 | 
			
		||||
        'title' => t('Display all values'),
 | 
			
		||||
        'method' => 'default_ignore',
 | 
			
		||||
        'breadcrumb' => TRUE, // generate a breadcrumb to here
 | 
			
		||||
      ),
 | 
			
		||||
      'not found' => array(
 | 
			
		||||
        'title' => t('Hide view / Page not found (404)'),
 | 
			
		||||
        'method' => 'default_not_found',
 | 
			
		||||
        'hard fail' => TRUE, // This is a hard fail condition
 | 
			
		||||
      ),
 | 
			
		||||
      'empty' => array(
 | 
			
		||||
        'title' => t('Display empty text'),
 | 
			
		||||
        'method' => 'default_empty',
 | 
			
		||||
        'breadcrumb' => TRUE, // generate a breadcrumb to here
 | 
			
		||||
      ),
 | 
			
		||||
      'default' => array(
 | 
			
		||||
        'title' => t('Provide default argument'),
 | 
			
		||||
        'method' => 'default_default',
 | 
			
		||||
        'form method' => 'default_argument_form',
 | 
			
		||||
        'has default argument' => TRUE,
 | 
			
		||||
        'default only' => TRUE, // this can only be used for missing argument, not validation failure
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if ($which) {
 | 
			
		||||
      return isset($defaults[$which]) ? $defaults[$which] : NULL;
 | 
			
		||||
    }
 | 
			
		||||
    return $defaults;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
 | 
			
		||||
    $options['break_phrase'] = array('default' => FALSE);
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    // Allow passing multiple values.
 | 
			
		||||
    $form['break_phrase'] = array(
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#title' => t('Allow multiple values'),
 | 
			
		||||
      '#description' => t('If selected, users can enter multiple values in the form of 1+2+3 (for OR) or 1,2,3 (for AND).'),
 | 
			
		||||
      '#default_value' => $this->options['break_phrase'],
 | 
			
		||||
      '#fieldset' => 'more',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set up the query for this argument.
 | 
			
		||||
   *
 | 
			
		||||
   * The argument sent may be found at $this->argument.
 | 
			
		||||
   */
 | 
			
		||||
  // @todo Provide options to select the operator, instead of always using '='?
 | 
			
		||||
  public function query($group_by = FALSE) {
 | 
			
		||||
    if (!empty($this->options['break_phrase'])) {
 | 
			
		||||
      views_break_phrase($this->argument, $this);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->value = array($this->argument);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (count($this->value) > 1) {
 | 
			
		||||
      $filter = $this->query->createFilter(drupal_strtoupper($this->operator));
 | 
			
		||||
      // $filter will be NULL if there were errors in the query.
 | 
			
		||||
      if ($filter) {
 | 
			
		||||
        foreach ($this->value as $value) {
 | 
			
		||||
          $filter->condition($this->real_field, $value, '=');
 | 
			
		||||
        }
 | 
			
		||||
        $this->query->filter($filter);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->query->condition($this->real_field, reset($this->value));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the title this argument will assign the view, given the argument.
 | 
			
		||||
   *
 | 
			
		||||
   * This usually needs to be overridden to provide a proper title.
 | 
			
		||||
   */
 | 
			
		||||
  public function title() {
 | 
			
		||||
    return t('Search @field for "@arg"', array('@field' => $this->definition['title'], '@arg' => $this->argument));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views argument handler class for handling fulltext fields.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerArgumentFulltext extends SearchApiViewsHandlerArgumentText {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Specify the options this filter uses.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
    $options['fields'] = array('default' => array());
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extend the options form a bit.
 | 
			
		||||
   */
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $fields = $this->getFulltextFields();
 | 
			
		||||
    if (!empty($fields)) {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Searched fields'),
 | 
			
		||||
        '#description' => t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'),
 | 
			
		||||
        '#options' => $fields,
 | 
			
		||||
        '#size' => min(4, count($fields)),
 | 
			
		||||
        '#multiple' => TRUE,
 | 
			
		||||
        '#default_value' => $this->options['fields'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set up the query for this argument.
 | 
			
		||||
   *
 | 
			
		||||
   * The argument sent may be found at $this->argument.
 | 
			
		||||
   */
 | 
			
		||||
  public function query($group_by = FALSE) {
 | 
			
		||||
    if ($this->options['fields']) {
 | 
			
		||||
      $this->query->fields($this->options['fields']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $old = $this->query->getOriginalKeys();
 | 
			
		||||
    $this->query->keys($this->argument);
 | 
			
		||||
    if ($old) {
 | 
			
		||||
      $keys = &$this->query->getKeys();
 | 
			
		||||
      if (is_array($keys)) {
 | 
			
		||||
        $keys[] = $old;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (is_array($old)) {
 | 
			
		||||
        // We don't support such nonsense.
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $keys = "($old) ($keys)";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method to get an option list of all available fulltext fields.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFulltextFields() {
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    $index = search_api_index_load(substr($this->table, 17));
 | 
			
		||||
    if (!empty($index->options['fields'])) {
 | 
			
		||||
      $fields = $index->getFields();
 | 
			
		||||
      foreach ($index->getFulltextFields() as $field) {
 | 
			
		||||
        $ret[$field] = $fields[$field]['name'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,77 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views argument handler providing a list of related items for search servers
 | 
			
		||||
 * supporting the "search_api_mlt" feature.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArgument {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Specify the options this filter uses.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
    unset($options['break_phrase']);
 | 
			
		||||
    $options['fields'] = array('default' => array());
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extend the options form a bit.
 | 
			
		||||
   */
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
    unset($form['break_phrase']);
 | 
			
		||||
 | 
			
		||||
    $index = search_api_index_load(substr($this->table, 17));
 | 
			
		||||
    if (!empty($index->options['fields'])) {
 | 
			
		||||
      $fields = array();
 | 
			
		||||
      foreach ($index->getFields() as $key => $field) {
 | 
			
		||||
        $fields[$key] = $field['name'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($fields)) {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Fields for Similarity'),
 | 
			
		||||
        '#description' => t('Select the fields that will be used for finding similar content. If no fields are selected, all available fields will be used.'),
 | 
			
		||||
        '#options' => $fields,
 | 
			
		||||
        '#size' => min(8, count($fields)),
 | 
			
		||||
        '#multiple' => TRUE,
 | 
			
		||||
        '#default_value' => $this->options['fields'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set up the query for this argument.
 | 
			
		||||
   *
 | 
			
		||||
   * The argument sent may be found at $this->argument.
 | 
			
		||||
   */
 | 
			
		||||
  public function query($group_by = FALSE) {
 | 
			
		||||
    $server = $this->query->getIndex()->server();
 | 
			
		||||
    if (!$server->supportsFeature('search_api_mlt')) {
 | 
			
		||||
      $class = search_api_get_service_info($server->class);
 | 
			
		||||
      throw new SearchApiException(t('The search service "@class" does not offer "More like this" functionality.',
 | 
			
		||||
          array('@class' => $class['name'])));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $fields = $this->options['fields'] ? $this->options['fields'] : array();
 | 
			
		||||
    if (empty($fields)) {
 | 
			
		||||
      foreach ($this->query->getIndex()->options['fields'] as $key => $field) {
 | 
			
		||||
        $fields[] = $key;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $mlt = array(
 | 
			
		||||
      'id' => $this->argument,
 | 
			
		||||
      'fields' => $fields,
 | 
			
		||||
    );
 | 
			
		||||
    $this->query->getSearchApiQuery()->setOption('search_api_mlt', $mlt);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								contrib/search_api_views/includes/handler_argument_text.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								contrib/search_api_views/includes/handler_argument_text.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views argument handler class for handling fulltext fields.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerArgumentText extends SearchApiViewsHandlerArgument {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the title this argument will assign the view, given the argument.
 | 
			
		||||
   *
 | 
			
		||||
   * This usually needs to be overridden to provide a proper title.
 | 
			
		||||
   */
 | 
			
		||||
  public function title() {
 | 
			
		||||
    return t('Search for "@arg"', array('@field' => $this->definition['title'], '@arg' => $this->argument));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								contrib/search_api_views/includes/handler_filter.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								contrib/search_api_views/includes/handler_filter.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler base class for handling all "normal" cases.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilter extends views_handler_filter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The value to filter for.
 | 
			
		||||
   *
 | 
			
		||||
   * @var mixed
 | 
			
		||||
   */
 | 
			
		||||
  public $value;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The operator used for filtering.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $operator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated views query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiViewsQuery
 | 
			
		||||
   */
 | 
			
		||||
  public $query;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a list of options for the operator form.
 | 
			
		||||
   */
 | 
			
		||||
  public function operator_options() {
 | 
			
		||||
    return array(
 | 
			
		||||
      '<' => t('Is smaller than'),
 | 
			
		||||
      '<=' => t('Is smaller than or equal to'),
 | 
			
		||||
      '=' => t('Is equal to'),
 | 
			
		||||
      '<>' => t('Is not equal to'),
 | 
			
		||||
      '>=' => t('Is greater than or equal to'),
 | 
			
		||||
      '>' => t('Is greater than'),
 | 
			
		||||
      'empty' => t('Is empty'),
 | 
			
		||||
      'not empty' => t('Is not empty'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a form for setting the filter value.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_form(&$form, &$form_state) {
 | 
			
		||||
    while (is_array($this->value)) {
 | 
			
		||||
      $this->value = $this->value ? array_shift($this->value) : NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $form['value'] = array(
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
      '#title' => empty($form_state['exposed']) ? t('Value') : '',
 | 
			
		||||
      '#size' => 30,
 | 
			
		||||
      '#default_value' => isset($this->value) ? $this->value : '',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Hide the value box if the operator is 'empty' or 'not empty'.
 | 
			
		||||
    // Radios share the same selector so we have to add some dummy selector.
 | 
			
		||||
    $form['value']['#states']['visible'] = array(
 | 
			
		||||
      ':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
 | 
			
		||||
      ':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display the filter on the administrative summary
 | 
			
		||||
   */
 | 
			
		||||
  function admin_summary() {
 | 
			
		||||
    if (!empty($this->options['exposed'])) {
 | 
			
		||||
      return t('exposed');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($this->operator === 'empty') {
 | 
			
		||||
      return t('is empty');
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->operator === 'not empty') {
 | 
			
		||||
      return t('is not empty');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return check_plain((string) $this->operator) . ' ' . check_plain((string) $this->value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add this filter to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    if ($this->operator === 'empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($this->operator === 'not empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      while (is_array($this->value)) {
 | 
			
		||||
        $this->value = $this->value ? reset($this->value) : NULL;
 | 
			
		||||
      }
 | 
			
		||||
      if (strlen($this->value) > 0) {
 | 
			
		||||
        $this->query->condition($this->real_field, $this->value, $this->operator, $this->options['group']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								contrib/search_api_views/includes/handler_filter_boolean.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								contrib/search_api_views/includes/handler_filter_boolean.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler class for handling fulltext fields.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterBoolean extends SearchApiViewsHandlerFilter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a list of options for the operator form.
 | 
			
		||||
   */
 | 
			
		||||
  public function operator_options() {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a form for setting the filter value.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_form(&$form, &$form_state) {
 | 
			
		||||
    while (is_array($this->value)) {
 | 
			
		||||
      $this->value = $this->value ? array_shift($this->value) : NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $form['value'] = array(
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#title' => empty($form_state['exposed']) ? t('Value') : '',
 | 
			
		||||
      '#options' => array(1 => t('True'), 0 => t('False')),
 | 
			
		||||
      '#default_value' => isset($this->value) ? $this->value : '',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								contrib/search_api_views/includes/handler_filter_date.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								contrib/search_api_views/includes/handler_filter_date.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler base class for handling all "normal" cases.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a "widget type" option.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    return parent::option_definition() + array(
 | 
			
		||||
      'widget_type' => array('default' => 'default'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If the date popup module is enabled, provide the extra option setting.
 | 
			
		||||
   */
 | 
			
		||||
  public function has_extra_options() {
 | 
			
		||||
    if (module_exists('date_popup')) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add extra options if we allow the date popup widget.
 | 
			
		||||
   */
 | 
			
		||||
  public function extra_options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::extra_options_form($form, $form_state);
 | 
			
		||||
    if (module_exists('date_popup')) {
 | 
			
		||||
      $widget_options = array('default' => 'Default', 'date_popup' => 'Date popup');
 | 
			
		||||
      $form['widget_type'] = array(
 | 
			
		||||
        '#type' => 'radios',
 | 
			
		||||
        '#title' => t('Date selection form element'),
 | 
			
		||||
        '#default_value' => $this->options['widget_type'],
 | 
			
		||||
        '#options' => $widget_options,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a form for setting the filter value.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_form(&$form, &$form_state) {
 | 
			
		||||
    parent::value_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    // If we are using the date popup widget, overwrite the settings of the form
 | 
			
		||||
    // according to what date_popup expects.
 | 
			
		||||
    if ($this->options['widget_type'] == 'date_popup' && module_exists('date_popup')) {
 | 
			
		||||
      $form['value']['#type'] = 'date_popup';
 | 
			
		||||
      $form['value']['#date_format'] = 'm/d/Y';
 | 
			
		||||
      unset($form['value']['#description']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (empty($form_state['exposed'])) {
 | 
			
		||||
      $form['value']['#description'] = t('A date in any format understood by <a href="@doc-link">PHP</a>. For example, "@date1" or "@date2".', array(
 | 
			
		||||
        '@doc-link' => 'http://php.net/manual/en/function.strtotime.php',
 | 
			
		||||
        '@date1' => format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s'),
 | 
			
		||||
        '@date2' => 'now + 1 day',
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add this filter to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    if ($this->operator === 'empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($this->operator === 'not empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      while (is_array($this->value)) {
 | 
			
		||||
        $this->value = $this->value ? reset($this->value) : NULL;
 | 
			
		||||
      }
 | 
			
		||||
      $v = is_numeric($this->value) ? $this->value : strtotime($this->value, REQUEST_TIME);
 | 
			
		||||
      if ($v !== FALSE) {
 | 
			
		||||
        $this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								contrib/search_api_views/includes/handler_filter_fulltext.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								contrib/search_api_views/includes/handler_filter_fulltext.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler class for handling fulltext fields.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterText {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Specify the options this filter uses.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
 | 
			
		||||
    $options['mode'] = array('default' => 'keys');
 | 
			
		||||
    $options['fields'] = array('default' => array());
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extend the options form a bit.
 | 
			
		||||
   */
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $form['mode'] = array(
 | 
			
		||||
      '#title' => t('Use as'),
 | 
			
		||||
      '#type' => 'radios',
 | 
			
		||||
      '#options' => array(
 | 
			
		||||
        'keys' => t('Search keys – multiple words will be split and the filter will influence relevance.'),
 | 
			
		||||
        'filter' => t("Search filter – use as a single phrase that restricts the result set but doesn't influence relevance."),
 | 
			
		||||
      ),
 | 
			
		||||
      '#default_value' => $this->options['mode'],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $fields = $this->getFulltextFields();
 | 
			
		||||
    if (!empty($fields)) {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Searched fields'),
 | 
			
		||||
        '#description' => t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'),
 | 
			
		||||
        '#options' => $fields,
 | 
			
		||||
        '#size' => min(4, count($fields)),
 | 
			
		||||
        '#multiple' => TRUE,
 | 
			
		||||
        '#default_value' => $this->options['fields'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['fields'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($form['expose'])) {
 | 
			
		||||
      $form['expose']['#weight'] = -5;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add this filter to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    while (is_array($this->value)) {
 | 
			
		||||
      $this->value = $this->value ? reset($this->value) : '';
 | 
			
		||||
    }
 | 
			
		||||
    // Catch empty strings entered by the user, but not "0".
 | 
			
		||||
    if ($this->value === '') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $fields = $this->options['fields'];
 | 
			
		||||
    $fields = $fields ? $fields : array_keys($this->getFulltextFields());
 | 
			
		||||
 | 
			
		||||
    // If something already specifically set different fields, we silently fall
 | 
			
		||||
    // back to mere filtering.
 | 
			
		||||
    $filter = $this->options['mode'] == 'filter';
 | 
			
		||||
    if (!$filter) {
 | 
			
		||||
      $old = $this->query->getFields();
 | 
			
		||||
      $filter = $old && (array_diff($old, $fields) || array_diff($fields, $old));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($filter) {
 | 
			
		||||
      $filter = $this->query->createFilter('OR');
 | 
			
		||||
      foreach ($fields as $field) {
 | 
			
		||||
        $filter->condition($field, $this->value, $this->operator);
 | 
			
		||||
      }
 | 
			
		||||
      $this->query->filter($filter);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->query->fields($fields);
 | 
			
		||||
    $old = $this->query->getOriginalKeys();
 | 
			
		||||
    $this->query->keys($this->value);
 | 
			
		||||
    if ($this->operator != '=') {
 | 
			
		||||
      $keys = &$this->query->getKeys();
 | 
			
		||||
      if (is_array($keys)) {
 | 
			
		||||
        $keys['#negation'] = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // We can't know how negation is expressed in the server's syntax.
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($old) {
 | 
			
		||||
      $keys = &$this->query->getKeys();
 | 
			
		||||
      if (is_array($keys)) {
 | 
			
		||||
        $keys[] = $old;
 | 
			
		||||
      }
 | 
			
		||||
      elseif (is_array($old)) {
 | 
			
		||||
        // We don't support such nonsense.
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $keys = "($old) ($keys)";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method to get an option list of all available fulltext fields.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getFulltextFields() {
 | 
			
		||||
    $fields = array();
 | 
			
		||||
    $index = search_api_index_load(substr($this->table, 17));
 | 
			
		||||
    if (!empty($index->options['fields'])) {
 | 
			
		||||
      $f = $index->getFields();
 | 
			
		||||
      foreach ($index->getFulltextFields() as $name) {
 | 
			
		||||
        $fields[$name] = $f[$name]['name'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $fields;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 *   Contains the SearchApiViewsHandlerFilterLanguage class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler class for handling the special "Item language" field.
 | 
			
		||||
 *
 | 
			
		||||
 * Definition items:
 | 
			
		||||
 * - options: An array of possible values for this field.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOptions {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a form for setting options.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_form(&$form, &$form_state) {
 | 
			
		||||
    parent::value_form($form, $form_state);
 | 
			
		||||
    $form['value']['#options'] = array(
 | 
			
		||||
      'current' => t("Current user's language"),
 | 
			
		||||
      'default' => t('Default site language'),
 | 
			
		||||
    ) + $form['value']['#options'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a summary of this filter's value for the admin UI.
 | 
			
		||||
   */
 | 
			
		||||
  public function admin_summary() {
 | 
			
		||||
    $tmp = $this->definition['options'];
 | 
			
		||||
    $this->definition['options']['current'] = t('current');
 | 
			
		||||
    $this->definition['options']['default'] = t('default');
 | 
			
		||||
    $ret = parent::admin_summary();
 | 
			
		||||
    $this->definition['options'] = $tmp;
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add this filter to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    global $language_content;
 | 
			
		||||
    foreach ($this->value as $i => $v) {
 | 
			
		||||
      if ($v == 'current') {
 | 
			
		||||
        $this->value[$i] = $language_content->language;
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($v == 'default') {
 | 
			
		||||
        $this->value[$i] = language_default('language');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    parent::query();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										205
									
								
								contrib/search_api_views/includes/handler_filter_options.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								contrib/search_api_views/includes/handler_filter_options.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,205 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler class for handling fields with a limited set of possible
 | 
			
		||||
 * values.
 | 
			
		||||
 *
 | 
			
		||||
 * Definition items:
 | 
			
		||||
 * - options: An array of possible values for this field.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
 | 
			
		||||
 | 
			
		||||
  protected $value_form_type = 'checkboxes';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a list of options for the operator form.
 | 
			
		||||
   */
 | 
			
		||||
  public function operator_options() {
 | 
			
		||||
    return array(
 | 
			
		||||
      '=' => t('Is one of'),
 | 
			
		||||
      '<>' => t('Is not one of'),
 | 
			
		||||
      'empty' => t('Is empty'),
 | 
			
		||||
      'not empty' => t('Is not empty'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set "reduce" option to FALSE by default.
 | 
			
		||||
   */
 | 
			
		||||
  public function expose_options() {
 | 
			
		||||
    parent::expose_options();
 | 
			
		||||
    $this->options['expose']['reduce'] = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add the "reduce" option to the exposed form.
 | 
			
		||||
   */
 | 
			
		||||
  public function expose_form(&$form, &$form_state) {
 | 
			
		||||
    parent::expose_form($form, $form_state);
 | 
			
		||||
    $form['expose']['reduce'] = array(
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#title' => t('Limit list to selected items'),
 | 
			
		||||
      '#description' => t('If checked, the only items presented to the user will be the ones selected here.'),
 | 
			
		||||
      '#default_value' => !empty($this->options['expose']['reduce']),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Define "reduce" option.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    $options = parent::option_definition();
 | 
			
		||||
    $options['expose']['contains']['reduce'] = array('default' => FALSE);
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reduce the options according to the selection.
 | 
			
		||||
   */
 | 
			
		||||
  protected function reduce_value_options() {
 | 
			
		||||
    $options = array();
 | 
			
		||||
    foreach ($this->definition['options'] as $id => $option) {
 | 
			
		||||
      if (isset($this->options['value'][$id])) {
 | 
			
		||||
        $options[$id] = $option;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Save set checkboxes.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_submit($form, &$form_state) {
 | 
			
		||||
    // Drupal's FAPI system automatically puts '0' in for any checkbox that
 | 
			
		||||
    // was not set, and the key to the checkbox if it is set.
 | 
			
		||||
    // Unfortunately, this means that if the key to that checkbox is 0,
 | 
			
		||||
    // we are unable to tell if that checkbox was set or not.
 | 
			
		||||
 | 
			
		||||
    // Luckily, the '#value' on the checkboxes form actually contains
 | 
			
		||||
    // *only* a list of checkboxes that were set, and we can use that
 | 
			
		||||
    // instead.
 | 
			
		||||
 | 
			
		||||
    $form_state['values']['options']['value'] = $form['value']['#value'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a form for setting options.
 | 
			
		||||
   */
 | 
			
		||||
  public function value_form(&$form, &$form_state) {
 | 
			
		||||
    $options = array();
 | 
			
		||||
    if (!empty($this->options['expose']['reduce']) && !empty($form_state['exposed'])) {
 | 
			
		||||
      $options += $this->reduce_value_options($form_state);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $options += $this->definition['options'];
 | 
			
		||||
    }
 | 
			
		||||
    $form['value'] = array(
 | 
			
		||||
      '#type' => $this->value_form_type,
 | 
			
		||||
      '#title' => empty($form_state['exposed']) ? t('Value') : '',
 | 
			
		||||
      '#options' => $options,
 | 
			
		||||
      '#multiple' => TRUE,
 | 
			
		||||
      '#size' => min(4, count($this->definition['options'])),
 | 
			
		||||
      '#default_value' => isset($this->value) ? $this->value : array(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Hide the value box if operator is 'empty' or 'not empty'.
 | 
			
		||||
    // Radios share the same selector so we have to add some dummy selector.
 | 
			
		||||
    // #states replace #dependency (http://drupal.org/node/1595022).
 | 
			
		||||
    $form['value']['#states']['visible'] = array(
 | 
			
		||||
      ':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
 | 
			
		||||
      ':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a summary of this filter's value for the admin UI.
 | 
			
		||||
   */
 | 
			
		||||
  public function admin_summary() {
 | 
			
		||||
    if (!empty($this->options['exposed'])) {
 | 
			
		||||
      return t('exposed');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($this->operator === 'empty') {
 | 
			
		||||
      return t('is empty');
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->operator === 'not empty') {
 | 
			
		||||
      return t('is not empty');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_array($this->value)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $operator_options = $this->operator_options();
 | 
			
		||||
    $operator = $operator_options[$this->operator];
 | 
			
		||||
    $values = '';
 | 
			
		||||
 | 
			
		||||
    // Remove every element which is not known.
 | 
			
		||||
    foreach ($this->value as $i => $value) {
 | 
			
		||||
      if (!isset($this->definition['options'][$value])) {
 | 
			
		||||
        unset($this->value[$i]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Choose different kind of ouput for 0, a single and multiple values.
 | 
			
		||||
    if (count($this->value) == 0) {
 | 
			
		||||
      return $this->operator == '=' ? t('none') : t('any');
 | 
			
		||||
    }
 | 
			
		||||
    elseif (count($this->value) == 1) {
 | 
			
		||||
      // If there is only a single value, use just the plain operator, = or <>.
 | 
			
		||||
      $operator = check_plain($this->operator);
 | 
			
		||||
      $values = check_plain($this->definition['options'][reset($this->value)]);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      foreach ($this->value as $value) {
 | 
			
		||||
        if ($values !== '') {
 | 
			
		||||
          $values .= ', ';
 | 
			
		||||
        }
 | 
			
		||||
        if (drupal_strlen($values) > 20) {
 | 
			
		||||
          $values .= '…';
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        $values .= check_plain($this->definition['options'][$value]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $operator . (($values !== '') ? ' ' . $values : '');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add this filter to the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    if ($this->operator === 'empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '=', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($this->operator === 'not empty') {
 | 
			
		||||
      $this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      while (is_array($this->value) && count($this->value) == 1) {
 | 
			
		||||
        $this->value = reset($this->value);
 | 
			
		||||
      }
 | 
			
		||||
      if (is_scalar($this->value) && $this->value !== '') {
 | 
			
		||||
        $this->query->condition($this->real_field, $this->value, $this->operator, $this->options['group']);
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($this->value) {
 | 
			
		||||
        if ($this->operator == '=') {
 | 
			
		||||
          $filter = $this->query->createFilter('OR');
 | 
			
		||||
          // $filter will be NULL if there were errors in the query.
 | 
			
		||||
          if ($filter) {
 | 
			
		||||
            foreach ($this->value as $v) {
 | 
			
		||||
              $filter->condition($this->real_field, $v, '=');
 | 
			
		||||
            }
 | 
			
		||||
            $this->query->filter($filter, $this->options['group']);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          foreach ($this->value as $v) {
 | 
			
		||||
            $this->query->condition($this->real_field, $v, $this->operator, $this->options['group']);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								contrib/search_api_views/includes/handler_filter_text.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								contrib/search_api_views/includes/handler_filter_text.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views filter handler class for handling fulltext fields.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerFilterText extends SearchApiViewsHandlerFilter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provide a list of options for the operator form.
 | 
			
		||||
   */
 | 
			
		||||
  public function operator_options() {
 | 
			
		||||
    return array('=' => t('contains'), '<>' => t("doesn't contain"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								contrib/search_api_views/includes/handler_sort.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								contrib/search_api_views/includes/handler_sort.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class for sorting results according to a specified field.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsHandlerSort extends views_handler_sort {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated views query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiViewsQuery
 | 
			
		||||
   */
 | 
			
		||||
  public $query;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called to add the sort to a query.
 | 
			
		||||
   */
 | 
			
		||||
  public function query() {
 | 
			
		||||
    // When there are exposed sorts, the "exposed form" plugin will set
 | 
			
		||||
    // $query->orderby to an empty array. Therefore, if that property is set,
 | 
			
		||||
    // we here remove all previous sorts.
 | 
			
		||||
    if (isset($this->query->orderby)) {
 | 
			
		||||
      unset($this->query->orderby);
 | 
			
		||||
      $sort = &$this->query->getSort();
 | 
			
		||||
      $sort = array();
 | 
			
		||||
    }
 | 
			
		||||
    $this->query->sort($this->real_field, $this->options['order']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										564
									
								
								contrib/search_api_views/includes/query.inc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										564
									
								
								contrib/search_api_views/includes/query.inc
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,564 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Views query class using a Search API index as the data source.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiViewsQuery extends views_plugin_query {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Number of results to display.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $limit;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Offset of first displayed result.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $offset;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The index this view accesses.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiIndex
 | 
			
		||||
   */
 | 
			
		||||
  protected $index;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The query that will be executed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiQueryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $query;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The results returned by the query, after it was executed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $search_api_results = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Array of all encountered errors.
 | 
			
		||||
   *
 | 
			
		||||
   * Each of these is fatal, meaning that a non-empty $errors property will
 | 
			
		||||
   * result in an empty result being returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $errors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The names of all fields whose value is required by a handler.
 | 
			
		||||
   *
 | 
			
		||||
   * The format follows the same as Search API field identifiers (parent:child).
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $fields;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The query's sub-filters representing the different Views filter groups.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $filters = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The conjunction with which multiple filter groups are combined.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $group_operator = 'AND';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create the basic query object and fill with default values.
 | 
			
		||||
   */
 | 
			
		||||
  public function init($base_table, $base_field, $options) {
 | 
			
		||||
    try {
 | 
			
		||||
      $this->errors = array();
 | 
			
		||||
      parent::init($base_table, $base_field, $options);
 | 
			
		||||
      $this->fields = array();
 | 
			
		||||
      if (substr($base_table, 0, 17) == 'search_api_index_') {
 | 
			
		||||
        $id = substr($base_table, 17);
 | 
			
		||||
        $this->index = search_api_index_load($id);
 | 
			
		||||
        $this->query = $this->index->query(array(
 | 
			
		||||
          'parse mode' => 'terms',
 | 
			
		||||
        ));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    catch (Exception $e) {
 | 
			
		||||
      $this->errors[] = $e->getMessage();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a field that should be retrieved from the results by this view.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $field
 | 
			
		||||
   *   The field's identifier, as used by the Search API. E.g., "title" for a
 | 
			
		||||
   *   node's title, "author:name" for a node's author's name.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiViewsQuery
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function addField($field) {
 | 
			
		||||
    $this->fields[$field] = TRUE;
 | 
			
		||||
    return $field;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a sort to the query.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $selector
 | 
			
		||||
   *   The field to sort on. All indexed fields of the index are valid values.
 | 
			
		||||
   *   In addition, the special fields 'search_api_relevance' (sort by
 | 
			
		||||
   *   relevance) and 'search_api_id' (sort by item id) may be used.
 | 
			
		||||
   * @param $order
 | 
			
		||||
   *   The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
 | 
			
		||||
   */
 | 
			
		||||
  public function add_selector_orderby($selector, $order = 'ASC') {
 | 
			
		||||
    $this->query->sort($selector, $order);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Defines the options used by this query plugin.
 | 
			
		||||
   *
 | 
			
		||||
   * Adds an option to bypass access checks.
 | 
			
		||||
   */
 | 
			
		||||
  public function option_definition() {
 | 
			
		||||
    return parent::option_definition() + array(
 | 
			
		||||
      'search_api_bypass_access' => array(
 | 
			
		||||
        'default' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add settings for the UI.
 | 
			
		||||
   *
 | 
			
		||||
   * Adds an option for bypassing access checks.
 | 
			
		||||
   */
 | 
			
		||||
  public function options_form(&$form, &$form_state) {
 | 
			
		||||
    parent::options_form($form, $form_state);
 | 
			
		||||
 | 
			
		||||
    $form['search_api_bypass_access'] = array(
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#title' => t('Bypass access checks'),
 | 
			
		||||
      '#description' => t('If the underlying search index has access checks enabled, this option allows to disable them for this view.'),
 | 
			
		||||
      '#default_value' => $this->options['search_api_bypass_access'],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the necessary info to execute the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function build(&$view) {
 | 
			
		||||
    $this->view = $view;
 | 
			
		||||
 | 
			
		||||
    // Setup the nested filter structure for this query.
 | 
			
		||||
    if (!empty($this->where)) {
 | 
			
		||||
      // If the different groups are combined with the OR operator, we have to
 | 
			
		||||
      // add a new OR filter to the query to which the filters for the groups
 | 
			
		||||
      // will be added.
 | 
			
		||||
      if ($this->group_operator === 'OR') {
 | 
			
		||||
        $base = $this->query->createFilter('OR');
 | 
			
		||||
        $this->query->filter($base);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $base = $this->query;
 | 
			
		||||
      }
 | 
			
		||||
      // Add a nested filter for each filter group, with its set conjunction.
 | 
			
		||||
      foreach ($this->where as $group_id => $group) {
 | 
			
		||||
        if (!empty($group['conditions']) || !empty($group['filters'])) {
 | 
			
		||||
          // For filters without a group, we want to always add them directly to
 | 
			
		||||
          // the query.
 | 
			
		||||
          $filter = ($group_id === '') ? $this->query : $this->query->createFilter($group['type']);
 | 
			
		||||
          if (!empty($group['conditions'])) {
 | 
			
		||||
            foreach ($group['conditions'] as $condition) {
 | 
			
		||||
              list($field, $value, $operator) = $condition;
 | 
			
		||||
              $filter->condition($field, $value, $operator);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (!empty($group['filters'])) {
 | 
			
		||||
            foreach ($group['filters'] as $nested_filter) {
 | 
			
		||||
              $filter->filter($nested_filter);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          // If no group was given, the filters were already set on the query.
 | 
			
		||||
          if ($group_id !== '') {
 | 
			
		||||
            $base->filter($filter);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize the pager and let it modify the query to add limits.
 | 
			
		||||
    $view->init_pager();
 | 
			
		||||
    $this->pager->query();
 | 
			
		||||
 | 
			
		||||
    // Add the "search_api_bypass_access" option to the query, if desired.
 | 
			
		||||
    if (!empty($this->options['search_api_bypass_access'])) {
 | 
			
		||||
      $this->query->setOption('search_api_bypass_access', TRUE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes the query and fills the associated view object with according
 | 
			
		||||
   * values.
 | 
			
		||||
   *
 | 
			
		||||
   * Values to set: $view->result, $view->total_rows, $view->execute_time,
 | 
			
		||||
   * $view->pager['current_page'].
 | 
			
		||||
   */
 | 
			
		||||
  public function execute(&$view) {
 | 
			
		||||
    if ($this->errors) {
 | 
			
		||||
      if (error_displayable()) {
 | 
			
		||||
        foreach ($this->errors as $msg) {
 | 
			
		||||
          drupal_set_message(check_plain($msg), 'error');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $view->result = array();
 | 
			
		||||
      $view->total_rows = 0;
 | 
			
		||||
      $view->execute_time = 0;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $start = microtime(TRUE);
 | 
			
		||||
      // Add range and search ID (if it wasn't already set).
 | 
			
		||||
      $this->query->range($this->offset, $this->limit);
 | 
			
		||||
      if ($this->query->getOption('search id') == get_class($this->query)) {
 | 
			
		||||
        $this->query->setOption('search id', 'search_api_views:' . $view->name . ':' . $view->current_display);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Execute the search.
 | 
			
		||||
      $results = $this->query->execute();
 | 
			
		||||
      $this->search_api_results = $results;
 | 
			
		||||
 | 
			
		||||
      // Store the results.
 | 
			
		||||
      $this->pager->total_items = $view->total_rows = $results['result count'];
 | 
			
		||||
      if (!empty($this->pager->options['offset'])) {
 | 
			
		||||
        $this->pager->total_items -= $this->pager->options['offset'];
 | 
			
		||||
      }
 | 
			
		||||
      $this->pager->update_page_info();
 | 
			
		||||
      $view->result = array();
 | 
			
		||||
      if (!empty($results['results'])) {
 | 
			
		||||
        $this->addResults($results['results'], $view);
 | 
			
		||||
      }
 | 
			
		||||
      // We shouldn't use $results['performance']['complete'] here, since
 | 
			
		||||
      // extracting the results probably takes considerable time as well.
 | 
			
		||||
      $view->execute_time = microtime(TRUE) - $start;
 | 
			
		||||
    }
 | 
			
		||||
    catch (Exception $e) {
 | 
			
		||||
      $this->errors[] = $e->getMessage();
 | 
			
		||||
      // Recursion to get the same error behaviour as above.
 | 
			
		||||
      return $this->execute($view);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper function for adding results to a view in the format expected by the
 | 
			
		||||
   * view.
 | 
			
		||||
   */
 | 
			
		||||
  protected function addResults(array $results, $view) {
 | 
			
		||||
    $rows = array();
 | 
			
		||||
    $missing = array();
 | 
			
		||||
    $items = array();
 | 
			
		||||
 | 
			
		||||
    // First off, we try to gather as much field values as possible without
 | 
			
		||||
    // loading any items.
 | 
			
		||||
    foreach ($results as $id => $result) {
 | 
			
		||||
      $row = array();
 | 
			
		||||
 | 
			
		||||
      // Include the loaded item for this result row, if present, or the item
 | 
			
		||||
      // ID.
 | 
			
		||||
      if (!empty($result['entity'])) {
 | 
			
		||||
        $row['entity'] = $result['entity'];
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $row['entity'] = $id;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $row['_entity_properties']['search_api_relevance'] = $result['score'];
 | 
			
		||||
      $row['_entity_properties']['search_api_excerpt'] = empty($result['excerpt']) ? '' : $result['excerpt'];
 | 
			
		||||
 | 
			
		||||
      // Gather any fields from the search results.
 | 
			
		||||
      if (!empty($result['fields'])) {
 | 
			
		||||
        $row['_entity_properties'] += $result['fields'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Check whether we need to extract any properties from the result item.
 | 
			
		||||
      $missing_fields = array_diff_key($this->fields, $row);
 | 
			
		||||
      if ($missing_fields) {
 | 
			
		||||
        $missing[$id] = $missing_fields;
 | 
			
		||||
        if (is_object($row['entity'])) {
 | 
			
		||||
          $items[$id] = $row['entity'];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $ids[] = $id;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Save the row values for adding them to the Views result afterwards.
 | 
			
		||||
      $rows[$id] = (object) $row;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load items of those rows which haven't got all field values, yet.
 | 
			
		||||
    if (!empty($ids)) {
 | 
			
		||||
      $items += $this->index->loadItems($ids);
 | 
			
		||||
      // $items now includes loaded items, and those already passed in the
 | 
			
		||||
      // search results.
 | 
			
		||||
      foreach ($items as $id => $item) {
 | 
			
		||||
        // Extract item properties.
 | 
			
		||||
        $wrapper = $this->index->entityWrapper($item, FALSE);
 | 
			
		||||
        $rows[$id]->_entity_properties += $this->extractFields($wrapper, $missing[$id]);
 | 
			
		||||
        $rows[$id]->entity = $item;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Finally, add all rows to the Views result set.
 | 
			
		||||
    $view->result = array_values($rows);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper function for extracting all necessary fields from a result item.
 | 
			
		||||
   *
 | 
			
		||||
   * Usually, this method isn't needed anymore as the properties are now
 | 
			
		||||
   * extracted by the field handlers themselves.
 | 
			
		||||
   */
 | 
			
		||||
  protected function extractFields(EntityMetadataWrapper $wrapper, array $all_fields) {
 | 
			
		||||
    $fields = array();
 | 
			
		||||
    foreach ($all_fields as $key => $true) {
 | 
			
		||||
      $fields[$key]['type'] = 'string';
 | 
			
		||||
    }
 | 
			
		||||
    $fields = search_api_extract_fields($wrapper, $fields, array('sanitized' => TRUE));
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    foreach ($all_fields as $key => $true) {
 | 
			
		||||
      $ret[$key] = isset($fields[$key]['value']) ? $fields[$key]['value'] : '';
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the according entity objects for the given query results.
 | 
			
		||||
   *
 | 
			
		||||
   * This is necessary to support generic entity handlers and plugins with this
 | 
			
		||||
   * query backend.
 | 
			
		||||
   *
 | 
			
		||||
   * If the current query isn't based on an entity type, the method will return
 | 
			
		||||
   * an empty array.
 | 
			
		||||
   */
 | 
			
		||||
  public function get_result_entities($results, $relationship = NULL, $field = NULL) {
 | 
			
		||||
    list($type, $wrappers) = $this->get_result_wrappers($results, $relationship, $field);
 | 
			
		||||
    $return = array();
 | 
			
		||||
    foreach ($wrappers as $id => $wrapper) {
 | 
			
		||||
      try {
 | 
			
		||||
        $return[$id] = $wrapper->value();
 | 
			
		||||
      }
 | 
			
		||||
      catch (EntityMetadataWrapperException $e) {
 | 
			
		||||
        // Ignore.
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return array($type, $return);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the according metadata wrappers for the given query results.
 | 
			
		||||
   *
 | 
			
		||||
   * This is necessary to support generic entity handlers and plugins with this
 | 
			
		||||
   * query backend.
 | 
			
		||||
   */
 | 
			
		||||
  public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
 | 
			
		||||
    $is_entity = (boolean) entity_get_info($this->index->item_type);
 | 
			
		||||
    $wrappers = array();
 | 
			
		||||
    $load_entities = array();
 | 
			
		||||
    foreach ($results as $row_index => $row) {
 | 
			
		||||
      if ($is_entity && isset($row->entity)) {
 | 
			
		||||
        // If this entity isn't load, register it for pre-loading.
 | 
			
		||||
        if (!is_object($row->entity)) {
 | 
			
		||||
          $load_entities[$row->entity] = $row_index;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $wrappers[$row_index] = $this->index->entityWrapper($row->entity);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the results are entities, we pre-load them to make use of a multiple
 | 
			
		||||
    // load. (Otherwise, each result would be loaded individually.)
 | 
			
		||||
    if (!empty($load_entities)) {
 | 
			
		||||
      $entities = entity_load($this->index->item_type, array_keys($load_entities));
 | 
			
		||||
      foreach ($entities as $entity_id => $entity) {
 | 
			
		||||
        $wrappers[$load_entities[$entity_id]] = $this->index->entityWrapper($entity);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Apply the relationship, if necessary.
 | 
			
		||||
    $type = $this->index->item_type;
 | 
			
		||||
    $selector_suffix = '';
 | 
			
		||||
    if ($field && ($pos = strrpos($field, ':'))) {
 | 
			
		||||
      $selector_suffix = substr($field, 0, $pos);
 | 
			
		||||
    }
 | 
			
		||||
    if ($selector_suffix || ($relationship && !empty($this->view->relationship[$relationship]))) {
 | 
			
		||||
      // Use EntityFieldHandlerHelper to compute the correct data selector for
 | 
			
		||||
      // the relationship.
 | 
			
		||||
      $handler = (object) array(
 | 
			
		||||
        'view' => $this->view,
 | 
			
		||||
        'relationship' => $relationship,
 | 
			
		||||
        'real_field' => '',
 | 
			
		||||
      );
 | 
			
		||||
      $selector = EntityFieldHandlerHelper::construct_property_selector($handler);
 | 
			
		||||
      $selector .= ($selector ? ':' : '') . $selector_suffix;
 | 
			
		||||
      list($type, $wrappers) = EntityFieldHandlerHelper::extract_property_multiple($wrappers, $selector);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return array($type, $wrappers);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * API function for accessing the raw Search API query object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiQueryInterface
 | 
			
		||||
   *   The search query object used internally by this handler.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSearchApiQuery() {
 | 
			
		||||
    return $this->query;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * API function for accessing the raw Search API results.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the search results, as specified by
 | 
			
		||||
   *   SearchApiQueryInterface::execute().
 | 
			
		||||
   */
 | 
			
		||||
  public function getSearchApiResults() {
 | 
			
		||||
    return $this->search_api_results;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //
 | 
			
		||||
  // Query interface methods (proxy to $this->query)
 | 
			
		||||
  //
 | 
			
		||||
 | 
			
		||||
  public function createFilter($conjunction = 'AND') {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->createFilter($conjunction);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function keys($keys = NULL) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->query->keys($keys);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function fields(array $fields) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->query->fields($fields);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a nested filter to the search query object.
 | 
			
		||||
   *
 | 
			
		||||
   * If $group is given, the filter is added to the relevant filter group
 | 
			
		||||
   * instead.
 | 
			
		||||
   */
 | 
			
		||||
  public function filter(SearchApiQueryFilterInterface $filter, $group = NULL) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->where[$group]['filters'][] = $filter;
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set a condition on the search query object.
 | 
			
		||||
   *
 | 
			
		||||
   * If $group is given, the condition is added to the relevant filter group
 | 
			
		||||
   * instead.
 | 
			
		||||
   */
 | 
			
		||||
  public function condition($field, $value, $operator = '=', $group = NULL) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->where[$group]['conditions'][] = array($field, $value, $operator);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function sort($field, $order = 'ASC') {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->query->sort($field, $order);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function range($offset = NULL, $limit = NULL) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      $this->query->range($offset, $limit);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getIndex() {
 | 
			
		||||
    return $this->index;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function &getKeys() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getKeys();
 | 
			
		||||
    }
 | 
			
		||||
    $ret = NULL;
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getOriginalKeys() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getOriginalKeys();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function &getFields() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getFields();
 | 
			
		||||
    }
 | 
			
		||||
    $ret = NULL;
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getFilter() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getFilter();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function &getSort() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getSort();
 | 
			
		||||
    }
 | 
			
		||||
    $ret = NULL;
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getOption($name) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getOption($name);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setOption($name, $value) {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->setOption($name, $value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function &getOptions() {
 | 
			
		||||
    if (!$this->errors) {
 | 
			
		||||
      return $this->query->getOptions();
 | 
			
		||||
    }
 | 
			
		||||
    $ret = NULL;
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								contrib/search_api_views/search_api_views.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								contrib/search_api_views/search_api_views.info
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
 | 
			
		||||
name = Search views
 | 
			
		||||
description = Integrates the Search API with Views, enabling users to create views with searches as filters or arguments.
 | 
			
		||||
dependencies[] = search_api
 | 
			
		||||
dependencies[] = views
 | 
			
		||||
core = 7.x
 | 
			
		||||
package = Search
 | 
			
		||||
 | 
			
		||||
; Views handlers
 | 
			
		||||
files[] = includes/display_facet_block.inc
 | 
			
		||||
files[] = includes/handler_argument.inc
 | 
			
		||||
files[] = includes/handler_argument_fulltext.inc
 | 
			
		||||
files[] = includes/handler_argument_more_like_this.inc
 | 
			
		||||
files[] = includes/handler_argument_text.inc
 | 
			
		||||
files[] = includes/handler_filter.inc
 | 
			
		||||
files[] = includes/handler_filter_boolean.inc
 | 
			
		||||
files[] = includes/handler_filter_date.inc
 | 
			
		||||
files[] = includes/handler_filter_fulltext.inc
 | 
			
		||||
files[] = includes/handler_filter_language.inc
 | 
			
		||||
files[] = includes/handler_filter_options.inc
 | 
			
		||||
files[] = includes/handler_filter_text.inc
 | 
			
		||||
files[] = includes/handler_sort.inc
 | 
			
		||||
files[] = includes/query.inc
 | 
			
		||||
 | 
			
		||||
; Information added by drupal.org packaging script on 2013-01-09
 | 
			
		||||
version = "7.x-1.4"
 | 
			
		||||
core = "7.x"
 | 
			
		||||
project = "search_api"
 | 
			
		||||
datestamp = "1357726719"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										97
									
								
								contrib/search_api_views/search_api_views.install
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								contrib/search_api_views/search_api_views.install
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Install, update and uninstall functions for the search_api_views module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates all Search API views to use the new, specification-compliant identifiers.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_update_7101() {
 | 
			
		||||
  $tables = views_fetch_data();
 | 
			
		||||
  // Contains arrays with real fields mapped to field IDs for each table.
 | 
			
		||||
  $table_fields = array();
 | 
			
		||||
  foreach ($tables as $key => $table) {
 | 
			
		||||
    if (substr($key, 0, 17) != 'search_api_index_') {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($table as $field => $info) {
 | 
			
		||||
      if (isset($info['real field']) && $field != $info['real field']) {
 | 
			
		||||
        $table_fields[$key][$info['real field']] = $field;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!$table_fields) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  foreach (views_get_all_views() as $name => $view) {
 | 
			
		||||
    if (empty($view->base_table) || empty($table_fields[$view->base_table])) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    $change = FALSE;
 | 
			
		||||
    $fields = $table_fields[$view->base_table];
 | 
			
		||||
    $change |= _search_api_views_update_7101_helper($view->base_field, $fields);
 | 
			
		||||
    if (!empty($view->display)) {
 | 
			
		||||
      foreach ($view->display as $key => &$display) {
 | 
			
		||||
        $options = &$display->display_options;
 | 
			
		||||
        if (isset($options['style_options']['grouping'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['style_options']['grouping'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['style_options']['columns'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['style_options']['columns'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['style_options']['info'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['style_options']['info'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['arguments'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['arguments'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['fields'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['fields'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['filters'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['filters'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($options['sorts'])) {
 | 
			
		||||
          $change |= _search_api_views_update_7101_helper($options['sorts'], $fields);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($change) {
 | 
			
		||||
      $view->save();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper function for replacing field identifiers.
 | 
			
		||||
 *
 | 
			
		||||
 * @return
 | 
			
		||||
 *   TRUE iff the identifier was changed.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_views_update_7101_helper(&$field, array $fields) {
 | 
			
		||||
  if (is_array($field)) {
 | 
			
		||||
    $change = FALSE;
 | 
			
		||||
    $new_field = array();
 | 
			
		||||
    foreach ($field as $k => $v) {
 | 
			
		||||
      $new_k = $k;
 | 
			
		||||
      $change |= _search_api_views_update_7101_helper($new_k, $fields);
 | 
			
		||||
      $change |= _search_api_views_update_7101_helper($v, $fields);
 | 
			
		||||
      $new_field[$new_k] = $v;
 | 
			
		||||
    }
 | 
			
		||||
    $field = $new_field;
 | 
			
		||||
    return $change;
 | 
			
		||||
  }
 | 
			
		||||
  if (isset($fields[$field])) {
 | 
			
		||||
    $field = $fields[$field];
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
  return FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Delete the now unnecessary "search_api_views_max_fields_depth" variable.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_update_7102() {
 | 
			
		||||
  variable_del('search_api_views_max_fields_depth');
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								contrib/search_api_views/search_api_views.module
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								contrib/search_api_views/search_api_views.module
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_views_api().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_views_api() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'api' => '3.0-alpha1',
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_search_api_index_insert().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_search_api_index_insert(SearchApiIndex $index) {
 | 
			
		||||
  // Make the new index available for views.
 | 
			
		||||
  views_invalidate_cache();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_search_api_index_update().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_search_api_index_update(SearchApiIndex $index) {
 | 
			
		||||
  if (!$index->enabled && $index->original->enabled) {
 | 
			
		||||
    _search_api_views_index_unavailable($index);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_search_api_index_delete().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_search_api_index_delete(SearchApiIndex $index) {
 | 
			
		||||
  _search_api_views_index_unavailable($index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function for reacting to a disabled or deleted search index.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_views_index_unavailable(SearchApiIndex $index) {
 | 
			
		||||
  $names = array();
 | 
			
		||||
  $table = 'search_api_index_' . $index->machine_name;
 | 
			
		||||
  foreach (views_get_all_views() as $name => $view) {
 | 
			
		||||
    if (empty($view->disabled) && $view->base_table == $table) {
 | 
			
		||||
      $names[] = $name;
 | 
			
		||||
      // @todo: if ($index_deleted) $view->delete()?
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if ($names) {
 | 
			
		||||
    views_invalidate_cache();
 | 
			
		||||
    drupal_set_message(t('The following views were using the index %name: @views. You should disable or delete them.', array('%name' => $index->name, '@views' => implode(', ', $names))), 'warning');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										196
									
								
								contrib/search_api_views/search_api_views.views.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								contrib/search_api_views/search_api_views.views.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_views_data().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_views_data() {
 | 
			
		||||
  try {
 | 
			
		||||
    $data = array();
 | 
			
		||||
    $entity_types = entity_get_info();
 | 
			
		||||
    foreach (search_api_index_load_multiple(FALSE) as $index) {
 | 
			
		||||
      // Fill in base data.
 | 
			
		||||
      $key = 'search_api_index_' . $index->machine_name;
 | 
			
		||||
      $table = &$data[$key];
 | 
			
		||||
      $type_info = search_api_get_item_type_info($index->item_type);
 | 
			
		||||
      $table['table']['group'] = t('Indexed @entity_type', array('@entity_type' => $type_info['name']));
 | 
			
		||||
      $table['table']['base'] = array(
 | 
			
		||||
        'field' => 'search_api_id',
 | 
			
		||||
        'index' => $index->machine_name,
 | 
			
		||||
        'title' => $index->name,
 | 
			
		||||
        'help' => t('Use the %name search index for filtering and retrieving data.', array('%name' => $index->name)),
 | 
			
		||||
        'query class' => 'search_api_views_query',
 | 
			
		||||
      );
 | 
			
		||||
      if (isset($entity_types[$index->item_type])) {
 | 
			
		||||
        $table['table'] += array(
 | 
			
		||||
          'entity type' => $index->item_type,
 | 
			
		||||
          'skip entity load' => TRUE,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $wrapper = $index->entityWrapper(NULL, TRUE);
 | 
			
		||||
 | 
			
		||||
      // Add field handlers and relationships provided by the Entity API.
 | 
			
		||||
      foreach ($wrapper as $key => $property) {
 | 
			
		||||
        $info = $property->info();
 | 
			
		||||
        if ($info) {
 | 
			
		||||
          entity_views_field_definition($key, $info, $table);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Add handlers for all indexed fields.
 | 
			
		||||
      foreach ($index->getFields() as $key => $field) {
 | 
			
		||||
        $tmp = $wrapper;
 | 
			
		||||
        $group = '';
 | 
			
		||||
        $name = '';
 | 
			
		||||
        $parts = explode(':', $key);
 | 
			
		||||
        foreach ($parts as $i => $part) {
 | 
			
		||||
          if (!isset($tmp->$part)) {
 | 
			
		||||
            continue 2;
 | 
			
		||||
          }
 | 
			
		||||
          $tmp = $tmp->$part;
 | 
			
		||||
          $info = $tmp->info();
 | 
			
		||||
          $group = ($group ? $group . ' » ' . $name : ($name ? $name : ''));
 | 
			
		||||
          $name = $info['label'];
 | 
			
		||||
          if ($i < count($parts) - 1) {
 | 
			
		||||
            // Unwrap lists.
 | 
			
		||||
            $level = search_api_list_nesting_level($info['type']);
 | 
			
		||||
            for ($j = 0; $j < $level; ++$j) {
 | 
			
		||||
              $tmp = $tmp[0];
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $id = _entity_views_field_identifier($key, $table);
 | 
			
		||||
        if ($group) {
 | 
			
		||||
          // @todo Entity type label instead of $group?
 | 
			
		||||
          $table[$id]['group'] = $group;
 | 
			
		||||
          $name = t('@field (indexed)', array('@field' => $name));
 | 
			
		||||
        }
 | 
			
		||||
        $table[$id]['title'] = $name;
 | 
			
		||||
        $table[$id]['help'] = empty($info['description']) ? t('(No information available)') : $info['description'];
 | 
			
		||||
        $table[$id]['type'] = $field['type'];
 | 
			
		||||
        if ($id != $key) {
 | 
			
		||||
          $table[$id]['real field'] = $key;
 | 
			
		||||
        }
 | 
			
		||||
        _search_api_views_add_handlers($key, $field, $tmp, $table);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Special handlers
 | 
			
		||||
      $table['search_api_language']['filter']['handler'] = 'SearchApiViewsHandlerFilterLanguage';
 | 
			
		||||
 | 
			
		||||
      $table['search_api_id']['title'] = t('Entity ID');
 | 
			
		||||
      $table['search_api_id']['help'] = t("The entity's ID.");
 | 
			
		||||
      $table['search_api_id']['sort']['handler'] = 'SearchApiViewsHandlerSort';
 | 
			
		||||
 | 
			
		||||
      $table['search_api_relevance']['group'] = t('Search');
 | 
			
		||||
      $table['search_api_relevance']['title'] = t('Relevance');
 | 
			
		||||
      $table['search_api_relevance']['help'] = t('The relevance of this search result with respect to the query.');
 | 
			
		||||
      $table['search_api_relevance']['field']['type'] = 'decimal';
 | 
			
		||||
      $table['search_api_relevance']['field']['handler'] = 'entity_views_handler_field_numeric';
 | 
			
		||||
      $table['search_api_relevance']['field']['click sortable'] = TRUE;
 | 
			
		||||
      $table['search_api_relevance']['sort']['handler'] = 'SearchApiViewsHandlerSort';
 | 
			
		||||
 | 
			
		||||
      $table['search_api_excerpt']['group'] = t('Search');
 | 
			
		||||
      $table['search_api_excerpt']['title'] = t('Excerpt');
 | 
			
		||||
      $table['search_api_excerpt']['help'] = t('The search result excerpted to show found search terms.');
 | 
			
		||||
      $table['search_api_excerpt']['field']['type'] = 'text';
 | 
			
		||||
      $table['search_api_excerpt']['field']['handler'] = 'entity_views_handler_field_text';
 | 
			
		||||
 | 
			
		||||
      $table['search_api_views_fulltext']['group'] = t('Search');
 | 
			
		||||
      $table['search_api_views_fulltext']['title'] = t('Fulltext search');
 | 
			
		||||
      $table['search_api_views_fulltext']['help'] = t('Search several or all fulltext fields at once.');
 | 
			
		||||
      $table['search_api_views_fulltext']['filter']['handler'] = 'SearchApiViewsHandlerFilterFulltext';
 | 
			
		||||
      $table['search_api_views_fulltext']['argument']['handler'] = 'SearchApiViewsHandlerArgumentFulltext';
 | 
			
		||||
 | 
			
		||||
      $table['search_api_views_more_like_this']['group'] = t('Search');
 | 
			
		||||
      $table['search_api_views_more_like_this']['title'] = t('More like this');
 | 
			
		||||
      $table['search_api_views_more_like_this']['help'] = t('Find similar content.');
 | 
			
		||||
      $table['search_api_views_more_like_this']['argument']['handler'] = 'SearchApiViewsHandlerArgumentMoreLikeThis';
 | 
			
		||||
    }
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
  catch (Exception $e) {
 | 
			
		||||
    watchdog_exception('search_api_views', $e);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper function that returns an array of handler definitions to add to a
 | 
			
		||||
 * views field definition.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper $wrapper, array &$table) {
 | 
			
		||||
  $type = $field['type'];
 | 
			
		||||
  $inner_type = search_api_extract_inner_type($type);
 | 
			
		||||
 | 
			
		||||
  if (strpos($id, ':')) {
 | 
			
		||||
    entity_views_field_definition($id, $wrapper->info(), $table);
 | 
			
		||||
  }
 | 
			
		||||
  $id = _entity_views_field_identifier($id, $table);
 | 
			
		||||
  $table += array($id => array());
 | 
			
		||||
 | 
			
		||||
  if ($inner_type == 'text') {
 | 
			
		||||
    $table[$id] += array(
 | 
			
		||||
      'argument' => array(
 | 
			
		||||
        'handler' => 'SearchApiViewsHandlerArgumentText',
 | 
			
		||||
      ),
 | 
			
		||||
      'filter' => array(
 | 
			
		||||
        'handler' => 'SearchApiViewsHandlerFilterText',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ($options = $wrapper->optionsList('view')) {
 | 
			
		||||
    $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterOptions';
 | 
			
		||||
    $table[$id]['filter']['options'] = $options;
 | 
			
		||||
  }
 | 
			
		||||
  elseif ($inner_type == 'boolean') {
 | 
			
		||||
    $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterBoolean';
 | 
			
		||||
  }
 | 
			
		||||
  elseif ($inner_type == 'date') {
 | 
			
		||||
    $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterDate';
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
 | 
			
		||||
 | 
			
		||||
  // We can only sort according to single-valued fields.
 | 
			
		||||
  if ($type == $inner_type) {
 | 
			
		||||
    $table[$id]['sort']['handler'] = 'SearchApiViewsHandlerSort';
 | 
			
		||||
    if (isset($table[$id]['field'])) {
 | 
			
		||||
      $table[$id]['field']['click sortable'] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_views_plugins().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_views_views_plugins() {
 | 
			
		||||
  $ret = array(
 | 
			
		||||
    'query' => array(
 | 
			
		||||
      'search_api_views_query' => array(
 | 
			
		||||
        'title' => t('Search API Query'),
 | 
			
		||||
        'help' => t('Query will be generated and run using the Search API.'),
 | 
			
		||||
        'handler' => 'SearchApiViewsQuery'
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (module_exists('search_api_facetapi')) {
 | 
			
		||||
    $ret['display']['search_api_views_facets_block'] = array(
 | 
			
		||||
      'title' => t('Facets block'),
 | 
			
		||||
      'help' => t('Display facets for this search as a block anywhere on the site.'),
 | 
			
		||||
      'handler' => 'SearchApiViewsFacetsBlockDisplay',
 | 
			
		||||
      'uses hook block' => TRUE,
 | 
			
		||||
      'use ajax' => FALSE,
 | 
			
		||||
      'use pager' => FALSE,
 | 
			
		||||
      'use more' => TRUE,
 | 
			
		||||
      'accept attachments' => TRUE,
 | 
			
		||||
      'admin' => t('Facets block'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return $ret;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								disabled.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								disabled.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 384 B  | 
							
								
								
									
										
											BIN
										
									
								
								enabled.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								enabled.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 383 B  | 
							
								
								
									
										220
									
								
								includes/callback.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								includes/callback.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,220 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a Search API data-alter callback.
 | 
			
		||||
 */
 | 
			
		||||
interface SearchApiAlterCallbackInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Construct a data-alter callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose items will be altered.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The callback options set for this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this data-alter callback is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * This can be used for hiding the callback on the index's "Workflow" tab. To
 | 
			
		||||
   * avoid confusion, you should only use criteria that are immutable, such as
 | 
			
		||||
   * the index's entity type. Also, since this is only used for UI purposes, you
 | 
			
		||||
   * should not completely rely on this to ensure certain index configurations
 | 
			
		||||
   * and at least throw an exception with a descriptive error message if this is
 | 
			
		||||
   * violated on runtime.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a form for configuring this callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for configuring this callback, or FALSE if no configuration
 | 
			
		||||
   *   is possible.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validation callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should both return the new options and set them internally.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The new options array for this callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alter items before indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Items which are removed from the array won't be indexed, but will be marked
 | 
			
		||||
   * as clean for future indexing. This could for instance be used to implement
 | 
			
		||||
   * some sort of access filter for security purposes (e.g., don't index
 | 
			
		||||
   * unpublished nodes or comments).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered, keyed by item IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function alterItems(array &$items);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Declare the properties that are (or can be) added to items with this
 | 
			
		||||
   * callback. If a property with this name already exists for an entity it
 | 
			
		||||
   * will be overridden, so keep a clear namespace by prefixing the properties
 | 
			
		||||
   * with the module name if this is not desired.
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Information about all additional properties, as specified by
 | 
			
		||||
   *   hook_entity_property_info() (only the inner "properties" array).
 | 
			
		||||
   */
 | 
			
		||||
  public function propertyInfo();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract base class for data-alter callbacks, implementing most methods with
 | 
			
		||||
 * sensible defaults.
 | 
			
		||||
 * Extending classes will at least have to implement the alterItems() method to
 | 
			
		||||
 * make this work. If that method adds additional fields to the items,
 | 
			
		||||
 * propertyInfo() has to be overridden, too.
 | 
			
		||||
 */
 | 
			
		||||
abstract class SearchApiAbstractAlterCallback implements SearchApiAlterCallbackInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The index whose items will be altered.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiIndex
 | 
			
		||||
   */
 | 
			
		||||
  protected $index;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration options for this callback, if it has any.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $options;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Construct a data-alter callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose items will be altered.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The callback options set for this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array()) {
 | 
			
		||||
    $this->index = $index;
 | 
			
		||||
    $this->options = $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this data-alter callback is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * This can be used for hiding the callback on the index's "Workflow" tab. To
 | 
			
		||||
   * avoid confusion, you should only use criteria that are immutable, such as
 | 
			
		||||
   * the index's entity type. Also, since this is only used for UI purposes, you
 | 
			
		||||
   * should not completely rely on this to ensure certain index configurations
 | 
			
		||||
   * and at least throw an exception with a descriptive error message if this is
 | 
			
		||||
   * violated on runtime.
 | 
			
		||||
   *
 | 
			
		||||
   * The default implementation always returns TRUE.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a form for configuring this callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for configuring this callback, or FALSE if no configuration
 | 
			
		||||
   *   is possible.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validation callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) { }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should both return the new options and set them internally.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The new options array for this callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $this->options = $values;
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Declare the properties that are (or can be) added to items with this
 | 
			
		||||
   * callback. If a property with this name already exists for an entity it
 | 
			
		||||
   * will be overridden, so keep a clear namespace by prefixing the properties
 | 
			
		||||
   * with the module name if this is not desired.
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Information about all additional properties, as specified by
 | 
			
		||||
   *   hook_entity_property_info() (only the inner "properties" array).
 | 
			
		||||
   */
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										313
									
								
								includes/callback_add_aggregation.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								includes/callback_add_aggregation.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,313 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that adds an URL field for all items.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
 | 
			
		||||
 | 
			
		||||
    $fields = $this->index->getFields(FALSE);
 | 
			
		||||
    $field_options = array();
 | 
			
		||||
    foreach ($fields as $name => $field) {
 | 
			
		||||
      $field_options[$name] = $field['name'];
 | 
			
		||||
    }
 | 
			
		||||
    $additional = empty($this->options['fields']) ? array() : $this->options['fields'];
 | 
			
		||||
 | 
			
		||||
    $types = $this->getTypes();
 | 
			
		||||
    $type_descriptions = $this->getTypes('description');
 | 
			
		||||
    $tmp = array();
 | 
			
		||||
    foreach ($types as $type => $name) {
 | 
			
		||||
      $tmp[$type] = array(
 | 
			
		||||
        '#type' => 'item',
 | 
			
		||||
        '#description' => $type_descriptions[$type],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $type_descriptions = $tmp;
 | 
			
		||||
 | 
			
		||||
    $form['#id'] = 'edit-callbacks-search-api-alter-add-aggregation-settings';
 | 
			
		||||
    $form['description'] = array(
 | 
			
		||||
      '#markup' => t('<p>This data alteration lets you define additional fields that will be added to this index. ' .
 | 
			
		||||
        'Each of these new fields will be an aggregation of one or more existing fields.</p>' .
 | 
			
		||||
        '<p>To add a new aggregated field, click the "Add new field" button and then fill out the form.</p>' .
 | 
			
		||||
        '<p>To remove a previously defined field, click the "Remove field" button.</p>' .
 | 
			
		||||
        '<p>You can also change the names or contained fields of existing aggregated fields.</p>'),
 | 
			
		||||
    );
 | 
			
		||||
    $form['fields']['#prefix'] = '<div id="search-api-alter-add-aggregation-field-settings">';
 | 
			
		||||
    $form['fields']['#suffix'] = '</div>';
 | 
			
		||||
    if (isset($this->changes)) {
 | 
			
		||||
      $form['fields']['#prefix'] .= '<div class="messages warning">All changes in the form will not be saved until the <em>Save configuration</em> button at the form bottom is clicked.</div>';
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($additional as $name => $field) {
 | 
			
		||||
      $form['fields'][$name] = array(
 | 
			
		||||
        '#type' => 'fieldset',
 | 
			
		||||
        '#title' => $field['name'] ? $field['name'] : t('New field'),
 | 
			
		||||
        '#collapsible' => TRUE,
 | 
			
		||||
        '#collapsed' => (boolean) $field['name'],
 | 
			
		||||
      );
 | 
			
		||||
      $form['fields'][$name]['name'] = array(
 | 
			
		||||
        '#type' => 'textfield',
 | 
			
		||||
        '#title' => t('New field name'),
 | 
			
		||||
        '#default_value' => $field['name'],
 | 
			
		||||
        '#required' => TRUE,
 | 
			
		||||
      );
 | 
			
		||||
      $form['fields'][$name]['type'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Aggregation type'),
 | 
			
		||||
        '#options' => $types,
 | 
			
		||||
        '#default_value' => $field['type'],
 | 
			
		||||
        '#required' => TRUE,
 | 
			
		||||
      );
 | 
			
		||||
      $form['fields'][$name]['type_descriptions'] = $type_descriptions;
 | 
			
		||||
      foreach (array_keys($types) as $type) {
 | 
			
		||||
        $form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]']['value'] = $type;
 | 
			
		||||
      }
 | 
			
		||||
      $form['fields'][$name]['fields'] = array(
 | 
			
		||||
        '#type' => 'checkboxes',
 | 
			
		||||
        '#title' => t('Contained fields'),
 | 
			
		||||
        '#options' => $field_options,
 | 
			
		||||
        '#default_value' => drupal_map_assoc($field['fields']),
 | 
			
		||||
        '#attributes' => array('class' => array('search-api-alter-add-aggregation-fields')),
 | 
			
		||||
        '#required' => TRUE,
 | 
			
		||||
      );
 | 
			
		||||
      $form['fields'][$name]['actions'] = array(
 | 
			
		||||
        '#type' => 'actions',
 | 
			
		||||
        'remove' => array(
 | 
			
		||||
          '#type' => 'submit',
 | 
			
		||||
          '#value' => t('Remove field'),
 | 
			
		||||
          '#submit' => array('_search_api_add_aggregation_field_submit'),
 | 
			
		||||
          '#limit_validation_errors' => array(),
 | 
			
		||||
          '#name' => 'search_api_add_aggregation_remove_' . $name,
 | 
			
		||||
          '#ajax' => array(
 | 
			
		||||
            'callback' => '_search_api_add_aggregation_field_ajax',
 | 
			
		||||
            'wrapper' => 'search-api-alter-add-aggregation-field-settings',
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $form['actions']['#type'] = 'actions';
 | 
			
		||||
    $form['actions']['add_field'] = array(
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => t('Add new field'),
 | 
			
		||||
      '#submit' => array('_search_api_add_aggregation_field_submit'),
 | 
			
		||||
      '#limit_validation_errors' => array(),
 | 
			
		||||
      '#ajax' => array(
 | 
			
		||||
        'callback' => '_search_api_add_aggregation_field_ajax',
 | 
			
		||||
        'wrapper' => 'search-api-alter-add-aggregation-field-settings',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    unset($values['actions']);
 | 
			
		||||
    if (empty($values['fields'])) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($values['fields'] as $name => $field) {
 | 
			
		||||
      $fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
 | 
			
		||||
      unset($values['fields'][$name]['actions']);
 | 
			
		||||
      if ($field['name'] && !$fields) {
 | 
			
		||||
        form_error($form['fields'][$name]['fields'], t('You have to select at least one field to aggregate. If you want to remove an aggregated field, please delete its name.'));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    if (empty($values['fields'])) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    $index_fields = $this->index->getFields(FALSE);
 | 
			
		||||
    foreach ($values['fields'] as $name => $field) {
 | 
			
		||||
      if (!$field['name']) {
 | 
			
		||||
        unset($values['fields'][$name]);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $values['fields'][$name]['description'] = $this->fieldDescription($field, $index_fields);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->options = $values;
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    if (!$items) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($this->options['fields'])) {
 | 
			
		||||
      $types = $this->getTypes('type');
 | 
			
		||||
      foreach ($items as $item) {
 | 
			
		||||
        $wrapper = $this->index->entityWrapper($item);
 | 
			
		||||
        foreach ($this->options['fields'] as $name => $field) {
 | 
			
		||||
          if ($field['name']) {
 | 
			
		||||
            $required_fields = array();
 | 
			
		||||
            foreach ($field['fields'] as $f) {
 | 
			
		||||
              if (!isset($required_fields[$f])) {
 | 
			
		||||
                $required_fields[$f]['type'] = $types[$field['type']];
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            $fields = search_api_extract_fields($wrapper, $required_fields);
 | 
			
		||||
            $values = array();
 | 
			
		||||
            foreach ($fields as $f) {
 | 
			
		||||
              if (isset($f['value'])) {
 | 
			
		||||
                $values[] = $f['value'];
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            $values = $this->flattenArray($values);
 | 
			
		||||
 | 
			
		||||
            $this->reductionType = $field['type'];
 | 
			
		||||
            $item->$name = array_reduce($values, array($this, 'reduce'), NULL);
 | 
			
		||||
            if ($field['type'] == 'count' && !$item->$name) {
 | 
			
		||||
              $item->$name = 0;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for reducing an array to a single value.
 | 
			
		||||
   */
 | 
			
		||||
  public function reduce($a, $b) {
 | 
			
		||||
    switch ($this->reductionType) {
 | 
			
		||||
      case 'fulltext':
 | 
			
		||||
        return isset($a) ? $a . "\n\n" . $b : $b;
 | 
			
		||||
      case 'sum':
 | 
			
		||||
        return $a + $b;
 | 
			
		||||
      case 'count':
 | 
			
		||||
        return $a + 1;
 | 
			
		||||
      case 'max':
 | 
			
		||||
        return isset($a) ? max($a, $b) : $b;
 | 
			
		||||
      case 'min':
 | 
			
		||||
        return isset($a) ? min($a, $b) : $b;
 | 
			
		||||
      case 'first':
 | 
			
		||||
        return isset($a) ? $a : $b;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for flattening a multi-dimensional array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function flattenArray(array $data) {
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    foreach ($data as $item) {
 | 
			
		||||
      if (!isset($item)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (is_scalar($item)) {
 | 
			
		||||
        $ret[] = $item;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $ret = array_merge($ret, $this->flattenArray($item));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    $types = $this->getTypes('type');
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    if (isset($this->options['fields'])) {
 | 
			
		||||
      foreach ($this->options['fields'] as $name => $field) {
 | 
			
		||||
        $ret[$name] = array(
 | 
			
		||||
          'label' => $field['name'],
 | 
			
		||||
          'description' => empty($field['description']) ? '' : $field['description'],
 | 
			
		||||
          'type' => $types[$field['type']],
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for creating a field description.
 | 
			
		||||
   */
 | 
			
		||||
  protected function fieldDescription(array $field, array $index_fields) {
 | 
			
		||||
    $fields = array();
 | 
			
		||||
    foreach ($field['fields'] as $f) {
 | 
			
		||||
      $fields[] = isset($index_fields[$f]) ? $index_fields[$f]['name'] : $f;
 | 
			
		||||
    }
 | 
			
		||||
    $type = $this->getTypes();
 | 
			
		||||
    $type = $type[$field['type']];
 | 
			
		||||
    return t('A @type aggregation of the following fields: @fields.', array('@type' => $type, '@fields' => implode(', ', $fields)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for getting all available aggregation types.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $info (optional)
 | 
			
		||||
   *   One of "name", "type" or "description", to indicate what values should be
 | 
			
		||||
   *   returned for the types. Defaults to "name".
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  protected function getTypes($info = 'name') {
 | 
			
		||||
    switch ($info) {
 | 
			
		||||
      case 'name':
 | 
			
		||||
        return array(
 | 
			
		||||
          'fulltext' => t('Fulltext'),
 | 
			
		||||
          'sum' => t('Sum'),
 | 
			
		||||
          'count' => t('Count'),
 | 
			
		||||
          'max' => t('Maximum'),
 | 
			
		||||
          'min' => t('Minimum'),
 | 
			
		||||
          'first' => t('First'),
 | 
			
		||||
        );
 | 
			
		||||
      case 'type':
 | 
			
		||||
        return array(
 | 
			
		||||
          'fulltext' => 'text',
 | 
			
		||||
          'sum' => 'integer',
 | 
			
		||||
          'count' => 'integer',
 | 
			
		||||
          'max' => 'integer',
 | 
			
		||||
          'min' => 'integer',
 | 
			
		||||
          'first' => 'string',
 | 
			
		||||
        );
 | 
			
		||||
      case 'description':
 | 
			
		||||
        return array(
 | 
			
		||||
          'fulltext' => t('The Fulltext aggregation concatenates the text data of all contained fields.'),
 | 
			
		||||
          'sum' => t('The Sum aggregation adds the values of all contained fields numerically.'),
 | 
			
		||||
          'count' => t('The Count aggregation takes the total number of contained field values as the aggregated field value.'),
 | 
			
		||||
          'max' => t('The Maximum aggregation computes the numerically largest contained field value.'),
 | 
			
		||||
          'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'),
 | 
			
		||||
          'first' => t('The First aggregation will simply keep the first encountered field value. This is helpful foremost when you know that a list field will only have a single value.'),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit helper callback for buttons in the callback's configuration form.
 | 
			
		||||
   */
 | 
			
		||||
  public function formButtonSubmit(array $form, array &$form_state) {
 | 
			
		||||
    $button_name = $form_state['triggering_element']['#name'];
 | 
			
		||||
    if ($button_name == 'op') {
 | 
			
		||||
      for ($i = 1; isset($this->options['fields']['search_api_aggregation_' . $i]); ++$i) {
 | 
			
		||||
      }
 | 
			
		||||
      $this->options['fields']['search_api_aggregation_' . $i] = array(
 | 
			
		||||
        'name' => '',
 | 
			
		||||
        'type' => 'fulltext',
 | 
			
		||||
        'fields' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $field = substr($button_name, 34);
 | 
			
		||||
      unset($this->options['fields'][$field]);
 | 
			
		||||
    }
 | 
			
		||||
    $form_state['rebuild'] = TRUE;
 | 
			
		||||
    $this->changes = TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Submit function for buttons in the callback's configuration form.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_add_aggregation_field_submit(array $form, array &$form_state) {
 | 
			
		||||
  $form_state['callbacks']['search_api_alter_add_aggregation']->formButtonSubmit($form, $form_state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AJAX submit function for buttons in the callback's configuration form.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_add_aggregation_field_ajax(array $form, array &$form_state) {
 | 
			
		||||
  return $form['callbacks']['settings']['search_api_alter_add_aggregation']['fields'];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										243
									
								
								includes/callback_add_hierarchy.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								includes/callback_add_hierarchy.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that adds an URL field for all items.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cached value for the hierarchical field options.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *
 | 
			
		||||
   * @see getHierarchicalFields()
 | 
			
		||||
   */
 | 
			
		||||
  protected $field_options;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Enable this data alteration only if any hierarchical fields are available.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return (bool) $this->getHierarchicalFields();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a form for configuring this callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for configuring this callback, or FALSE if no configuration
 | 
			
		||||
   *   is possible.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $options = $this->getHierarchicalFields();
 | 
			
		||||
    $this->options += array('fields' => array());
 | 
			
		||||
    $form['fields'] = array(
 | 
			
		||||
      '#title' => t('Hierarchical fields'),
 | 
			
		||||
      '#description' => t('Select the fields which should be supplemented with their ancestors. ' .
 | 
			
		||||
          'Each field is listed along with its children of the same type. ' .
 | 
			
		||||
          'When selecting several child properties of a field, all those properties will be recursively added to that field. ' .
 | 
			
		||||
          'Please note that you should de-select all fields before disabling this data alteration.'),
 | 
			
		||||
      '#type' => 'select',
 | 
			
		||||
      '#multiple' => TRUE,
 | 
			
		||||
      '#size' => min(6, count($options, COUNT_RECURSIVE)),
 | 
			
		||||
      '#options' => $options,
 | 
			
		||||
      '#default_value' => $this->options['fields'],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should both return the new options and set them internally.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The new options array for this callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    // Change the saved type of fields in the index, if necessary.
 | 
			
		||||
    if (!empty($this->index->options['fields'])) {
 | 
			
		||||
      $fields = &$this->index->options['fields'];
 | 
			
		||||
      $previous = drupal_map_assoc($this->options['fields']);
 | 
			
		||||
      foreach ($values['fields'] as $field) {
 | 
			
		||||
        list($key, $prop) = explode(':', $field);
 | 
			
		||||
        if (empty($previous[$field]) && isset($fields[$key]['type'])) {
 | 
			
		||||
          $fields[$key]['type'] = 'list<' . search_api_extract_inner_type($fields[$key]['type']) . '>';
 | 
			
		||||
          $change = TRUE;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $new = drupal_map_assoc($values['fields']);
 | 
			
		||||
      foreach ($previous as $field) {
 | 
			
		||||
        list($key, $prop) = explode(':', $field);
 | 
			
		||||
        if (empty($new[$field]) && isset($fields[$key]['type'])) {
 | 
			
		||||
          $w = $this->index->entityWrapper(NULL, FALSE);
 | 
			
		||||
          if (isset($w->$key)) {
 | 
			
		||||
            $type = $w->$key->type();
 | 
			
		||||
            $inner = search_api_extract_inner_type($fields[$key]['type']);
 | 
			
		||||
            $fields[$key]['type'] = search_api_nest_type($inner, $type);
 | 
			
		||||
            $change = TRUE;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($change)) {
 | 
			
		||||
        $this->index->save();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::configurationFormSubmit($form, $values, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alter items before indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Items which are removed from the array won't be indexed, but will be marked
 | 
			
		||||
   * as clean for future indexing. This could for instance be used to implement
 | 
			
		||||
   * some sort of access filter for security purposes (e.g., don't index
 | 
			
		||||
   * unpublished nodes or comments).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered, keyed by item IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    if (empty($this->options['fields'])) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($items as $item) {
 | 
			
		||||
      $wrapper = $this->index->entityWrapper($item, FALSE);
 | 
			
		||||
 | 
			
		||||
      $values = array();
 | 
			
		||||
      foreach ($this->options['fields'] as $field) {
 | 
			
		||||
        list($key, $prop) = explode(':', $field);
 | 
			
		||||
        if (!isset($wrapper->$key)) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $child = $wrapper->$key;
 | 
			
		||||
 | 
			
		||||
        $values += array($key => array());
 | 
			
		||||
        $this->extractHierarchy($child, $prop, $values[$key]);
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($values as $key => $value) {
 | 
			
		||||
        $item->$key = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Declare the properties that are (or can be) added to items with this
 | 
			
		||||
   * callback. If a property with this name already exists for an entity it
 | 
			
		||||
   * will be overridden, so keep a clear namespace by prefixing the properties
 | 
			
		||||
   * with the module name if this is not desired.
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Information about all additional properties, as specified by
 | 
			
		||||
   *   hook_entity_property_info() (only the inner "properties" array).
 | 
			
		||||
   */
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    if (empty($this->options['fields'])) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    $wrapper = $this->index->entityWrapper(NULL, FALSE);
 | 
			
		||||
    foreach ($this->options['fields'] as $field) {
 | 
			
		||||
      list($key, $prop) = explode(':', $field);
 | 
			
		||||
      if (!isset($wrapper->$key)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $child = $wrapper->$key;
 | 
			
		||||
      while (search_api_is_list_type($child->type())) {
 | 
			
		||||
        $child = $child[0];
 | 
			
		||||
      }
 | 
			
		||||
      if (!isset($child->$prop)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (!isset($ret[$key])) {
 | 
			
		||||
        $ret[$key] = $child->info();
 | 
			
		||||
        $type = search_api_extract_inner_type($ret[$key]['type']);
 | 
			
		||||
        $ret[$key]['type'] = "list<$type>";
 | 
			
		||||
        $ret[$key]['getter callback'] = 'entity_property_verbatim_get';
 | 
			
		||||
        // The return value of info() has some additional internal values set,
 | 
			
		||||
        // which we have to unset for the use here.
 | 
			
		||||
        unset($ret[$key]['name'], $ret[$key]['parent'], $ret[$key]['langcode'], $ret[$key]['clear'],
 | 
			
		||||
            $ret[$key]['property info alter'], $ret[$key]['property defaults']);
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($ret[$key]['bundle'])) {
 | 
			
		||||
        $info = $child->$prop->info();
 | 
			
		||||
        if (empty($info['bundle']) || $ret[$key]['bundle'] != $info['bundle']) {
 | 
			
		||||
          unset($ret[$key]['bundle']);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for finding all hierarchical fields of an index's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing all hierarchical fields of the index, structured as
 | 
			
		||||
   *   an options array grouped by primary field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getHierarchicalFields() {
 | 
			
		||||
    if (!isset($this->field_options)) {
 | 
			
		||||
      $this->field_options = array();
 | 
			
		||||
      $wrapper = $this->index->entityWrapper(NULL, FALSE);
 | 
			
		||||
      // Only entities can be indexed in hierarchies, as other properties don't
 | 
			
		||||
      // have IDs that we can extract and store.
 | 
			
		||||
      $entity_info = entity_get_info();
 | 
			
		||||
      foreach ($wrapper as $key1 => $child) {
 | 
			
		||||
        while (search_api_is_list_type($child->type())) {
 | 
			
		||||
          $child = $child[0];
 | 
			
		||||
        }
 | 
			
		||||
        $info = $child->info();
 | 
			
		||||
        $type = $child->type();
 | 
			
		||||
        if (empty($entity_info[$type])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($child as $key2 => $prop) {
 | 
			
		||||
          if (search_api_extract_inner_type($prop->type()) == $type) {
 | 
			
		||||
            $prop_info = $prop->info();
 | 
			
		||||
            $this->field_options[$info['label']]["$key1:$key2"] = $prop_info['label'];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->field_options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Extracts a hierarchy from a metadata wrapper by modifying $values.
 | 
			
		||||
   */
 | 
			
		||||
  public function extractHierarchy(EntityMetadataWrapper $wrapper, $property, array &$values) {
 | 
			
		||||
    if (search_api_is_list_type($wrapper->type())) {
 | 
			
		||||
      foreach ($wrapper as $w) {
 | 
			
		||||
        $this->extractHierarchy($w, $property, $values);
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $v = $wrapper->value(array('identifier' => TRUE));
 | 
			
		||||
    if ($v && !isset($values[$v])) {
 | 
			
		||||
      $values[$v] = $v;
 | 
			
		||||
      if (isset($wrapper->$property) && $wrapper->$property->value()) {
 | 
			
		||||
        $this->extractHierarchy($wrapper->$property, $property, $values);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								includes/callback_add_url.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								includes/callback_add_url.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that adds an URL field for all items.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterAddUrl extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    foreach ($items as $id => &$item) {
 | 
			
		||||
      $url = $this->index->datasource()->getItemUrl($item);
 | 
			
		||||
      if (!$url) {
 | 
			
		||||
        $item->search_api_url = NULL;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $item->search_api_url = url($url['path'], array('absolute' => TRUE) + $url['options']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'search_api_url' => array(
 | 
			
		||||
        'label' => t('URI'),
 | 
			
		||||
        'description' => t('An URI where the item can be accessed.'),
 | 
			
		||||
        'type' => 'uri',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								includes/callback_add_viewed_entity.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								includes/callback_add_viewed_entity.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that adds an URL field for all items.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Only support indexes containing entities.
 | 
			
		||||
   *
 | 
			
		||||
   * @see SearchApiAlterCallbackInterface::supportsIndex()
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return (bool) entity_get_info($index->item_type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $info = entity_get_info($this->index->item_type);
 | 
			
		||||
    $view_modes = array();
 | 
			
		||||
    foreach ($info['view modes'] as $key => $mode) {
 | 
			
		||||
      $view_modes[$key] = $mode['label'];
 | 
			
		||||
    }
 | 
			
		||||
    $this->options += array('mode' => reset($view_modes));
 | 
			
		||||
    if (count($view_modes) > 1) {
 | 
			
		||||
      $form['mode'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('View mode'),
 | 
			
		||||
        '#options' => $view_modes,
 | 
			
		||||
        '#default_value' => $this->options['mode'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['mode'] = array(
 | 
			
		||||
        '#type' => 'value',
 | 
			
		||||
        '#value' => $this->options['mode'],
 | 
			
		||||
      );
 | 
			
		||||
      if ($view_modes) {
 | 
			
		||||
        $form['note'] = array(
 | 
			
		||||
          '#markup' => '<p>' . t('Entities of type %type have only a single view mode. ' .
 | 
			
		||||
              'Therefore, no selection needs to be made.', array('%type' => $info['label'])) . '</p>',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $form['note'] = array(
 | 
			
		||||
          '#markup' => '<p>' . t('Entities of type %type have no defined view modes. ' .
 | 
			
		||||
              'This might either mean that they are always displayed the same way, or that they cannot be processed by this alteration at all. ' .
 | 
			
		||||
              'Please consider this when using this alteration.', array('%type' => $info['label'])) . '</p>',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    // Prevent session information from being saved while indexing.
 | 
			
		||||
    drupal_save_session(FALSE);
 | 
			
		||||
 | 
			
		||||
    // Force the current user to anonymous to prevent access bypass in search
 | 
			
		||||
    // indexes.
 | 
			
		||||
    $original_user = $GLOBALS['user'];
 | 
			
		||||
    $GLOBALS['user'] = drupal_anonymous_user();
 | 
			
		||||
 | 
			
		||||
    $type = $this->index->item_type;
 | 
			
		||||
    $mode = empty($this->options['mode']) ? 'full' : $this->options['mode'];
 | 
			
		||||
    foreach ($items as $id => &$item) {
 | 
			
		||||
      // Since we can't really know what happens in entity_view() and render(),
 | 
			
		||||
      // we use try/catch. This will at least prevent some errors, even though
 | 
			
		||||
      // it's no protection against fatal errors and the like.
 | 
			
		||||
      try {
 | 
			
		||||
        $render = entity_view($type, array(entity_id($type, $item) => $item), $mode);
 | 
			
		||||
        $text = render($render);
 | 
			
		||||
        if (!$text) {
 | 
			
		||||
          $item->search_api_viewed = NULL;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $item->search_api_viewed = $text;
 | 
			
		||||
      }
 | 
			
		||||
      catch (Exception $e) {
 | 
			
		||||
        $item->search_api_viewed = NULL;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Restore the user.
 | 
			
		||||
    $GLOBALS['user'] = $original_user;
 | 
			
		||||
    drupal_save_session(TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'search_api_viewed' => array(
 | 
			
		||||
        'label' => t('Entity HTML output'),
 | 
			
		||||
        'description' => t('The whole HTML content of the entity when viewed.'),
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								includes/callback_bundle_filter.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								includes/callback_bundle_filter.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that filters out items based on their
 | 
			
		||||
 * bundle.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return ($info = entity_get_info($index->item_type)) && self::hasBundles($info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    $info = entity_get_info($this->index->item_type);
 | 
			
		||||
    if (self::hasBundles($info) && isset($this->options['bundles'])) {
 | 
			
		||||
      $bundles = array_flip($this->options['bundles']);
 | 
			
		||||
      $default = (bool) $this->options['default'];
 | 
			
		||||
      $bundle_prop = $info['entity keys']['bundle'];
 | 
			
		||||
      foreach ($items as $id => $item) {
 | 
			
		||||
        if (isset($bundles[$item->$bundle_prop]) == $default) {
 | 
			
		||||
          unset($items[$id]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $info = entity_get_info($this->index->item_type);
 | 
			
		||||
    if (self::hasBundles($info)) {
 | 
			
		||||
      $options = array();
 | 
			
		||||
      foreach ($info['bundles'] as $bundle => $bundle_info) {
 | 
			
		||||
        $options[$bundle] = isset($bundle_info['label']) ? $bundle_info['label'] : $bundle;
 | 
			
		||||
      }
 | 
			
		||||
      $form = array(
 | 
			
		||||
        'default' => array(
 | 
			
		||||
          '#type' => 'radios',
 | 
			
		||||
          '#title' => t('Which items should be indexed?'),
 | 
			
		||||
          '#default_value' => isset($this->options['default']) ? $this->options['default'] : 1,
 | 
			
		||||
          '#options' => array(
 | 
			
		||||
            1 => t('All but those from one of the selected bundles'),
 | 
			
		||||
            0 => t('Only those from the selected bundles'),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        'bundles' => array(
 | 
			
		||||
          '#type' => 'select',
 | 
			
		||||
          '#title' => t('Bundles'),
 | 
			
		||||
          '#default_value' => isset($this->options['bundles']) ? $this->options['bundles'] : array(),
 | 
			
		||||
          '#options' => $options,
 | 
			
		||||
          '#size' => min(4, count($options)),
 | 
			
		||||
          '#multiple' => TRUE,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form = array(
 | 
			
		||||
        'forbidden' => array(
 | 
			
		||||
          '#markup' => '<p>' . t("Items indexed by this index don't have bundles and therefore cannot be filtered here.") . '</p>',
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for figuring out if the entities with the given entity info
 | 
			
		||||
   * can be filtered by bundle.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function hasBundles(array $entity_info) {
 | 
			
		||||
    return !empty($entity_info['entity keys']['bundle']) && !empty($entity_info['bundles']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										155
									
								
								includes/callback_language_control.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								includes/callback_language_control.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search API data alteration callback that filters out items based on their
 | 
			
		||||
 * bundle.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Construct a data-alter callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose items will be altered.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The callback options set for this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array()) {
 | 
			
		||||
    $options += array(
 | 
			
		||||
      'lang_field' => '',
 | 
			
		||||
      'languages' => array(),
 | 
			
		||||
    );
 | 
			
		||||
    parent::__construct($index, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this data-alter callback is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * Only returns TRUE if the system is multilingual.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see drupal_multilingual()
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return drupal_multilingual();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a form for configuring this data alteration.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for configuring this data alteration.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form = array();
 | 
			
		||||
 | 
			
		||||
    $wrapper = $this->index->entityWrapper();
 | 
			
		||||
    $fields[''] = t('- Use default -');
 | 
			
		||||
    foreach ($wrapper as $key => $property) {
 | 
			
		||||
      if ($key == 'search_api_language') {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $type = $property->type();
 | 
			
		||||
      // Only single-valued string properties make sense here. Also, nested
 | 
			
		||||
      // properties probably don't make sense.
 | 
			
		||||
      if ($type == 'text' || $type == 'token') {
 | 
			
		||||
        $info = $property->info();
 | 
			
		||||
        $fields[$key] = $info['label'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (count($fields) > 1) {
 | 
			
		||||
      $form['lang_field'] = array(
 | 
			
		||||
        '#type' => 'select',
 | 
			
		||||
        '#title' => t('Language field'),
 | 
			
		||||
        '#description' => t("Select the field which should be used to determine an item's language."),
 | 
			
		||||
        '#options' => $fields,
 | 
			
		||||
        '#default_value' => $this->options['lang_field'],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $languages[LANGUAGE_NONE] = t('Language neutral');
 | 
			
		||||
    $list = language_list('enabled') + array(array(), array());
 | 
			
		||||
    foreach (array($list[1], $list[0]) as $list) {
 | 
			
		||||
      foreach ($list as $lang) {
 | 
			
		||||
        $name = t($lang->name);
 | 
			
		||||
        $native = $lang->native;
 | 
			
		||||
        $languages[$lang->language] = ($name == $native) ? $name : "$name ($native)";
 | 
			
		||||
        if (!$lang->enabled) {
 | 
			
		||||
          $languages[$lang->language] .= ' [' . t('disabled') . ']';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $form['languages'] = array(
 | 
			
		||||
      '#type' => 'checkboxes',
 | 
			
		||||
      '#title' => t('Indexed languages'),
 | 
			
		||||
      '#description' => t('Index only items in the selected languages. ' .
 | 
			
		||||
          'When no language is selected, there will be no language-related restrictions.'),
 | 
			
		||||
      '#options' => $languages,
 | 
			
		||||
      '#default_value' => $this->options['languages'],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should both return the new options and set them internally.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The new options array for this callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $values['languages'] = array_filter($values['languages']);
 | 
			
		||||
    return parent::configurationFormSubmit($form, $values, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alter items before indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Items which are removed from the array won't be indexed, but will be marked
 | 
			
		||||
   * as clean for future indexing. This could for instance be used to implement
 | 
			
		||||
   * some sort of access filter for security purposes (e.g., don't index
 | 
			
		||||
   * unpublished nodes or comments).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered, keyed by item IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    foreach ($items as $i => &$item) {
 | 
			
		||||
      // Set item language, if a custom field was selected.
 | 
			
		||||
      if ($field = $this->options['lang_field']) {
 | 
			
		||||
        $wrapper = $this->index->entityWrapper($item);
 | 
			
		||||
        if (isset($wrapper->$field)) {
 | 
			
		||||
          try {
 | 
			
		||||
            $item->search_api_language = $wrapper->$field->value();
 | 
			
		||||
          }
 | 
			
		||||
          catch (EntityMetadataWrapperException $e) {
 | 
			
		||||
            // Something went wrong while accessing the language field. Probably
 | 
			
		||||
            // doesn't really matter.
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Filter out items according to language, if any were selected.
 | 
			
		||||
      if ($languages = $this->options['languages']) {
 | 
			
		||||
        if (empty($languages[$item->search_api_language])) {
 | 
			
		||||
          unset($items[$i]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										109
									
								
								includes/callback_node_access.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								includes/callback_node_access.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Contains the SearchApiAlterNodeAccess class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds node access information to node indexes.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this data-alter callback is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * Returns TRUE only for indexes on nodes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    // Currently only node access is supported.
 | 
			
		||||
    return $index->item_type === 'node';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Declare the properties that are (or can be) added to items with this callback.
 | 
			
		||||
   *
 | 
			
		||||
   * Adds the "search_api_access_node" property.
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Information about all additional properties, as specified by
 | 
			
		||||
   *   hook_entity_property_info() (only the inner "properties" array).
 | 
			
		||||
   */
 | 
			
		||||
  public function propertyInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'search_api_access_node' => array(
 | 
			
		||||
        'label' => t('Node access information'),
 | 
			
		||||
        'description' => t('Data needed to apply node access.'),
 | 
			
		||||
        'type' => 'list<token>',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alter items before indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Items which are removed from the array won't be indexed, but will be marked
 | 
			
		||||
   * as clean for future indexing. This could for instance be used to implement
 | 
			
		||||
   * some sort of access filter for security purposes (e.g., don't index
 | 
			
		||||
   * unpublished nodes or comments).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered, keyed by item IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    static $account;
 | 
			
		||||
 | 
			
		||||
    if (!isset($account)) {
 | 
			
		||||
      // Load the anonymous user.
 | 
			
		||||
      $account = drupal_anonymous_user();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($items as $nid => &$item) {
 | 
			
		||||
      // Check whether all users have access to the node.
 | 
			
		||||
      if (!node_access('view', $item, $account)) {
 | 
			
		||||
        // Get node access grants.
 | 
			
		||||
        $result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $item->nid));
 | 
			
		||||
 | 
			
		||||
        // Store all grants together with it's realms in the item.
 | 
			
		||||
        foreach ($result as $grant) {
 | 
			
		||||
          if (!isset($items[$nid]->search_api_access_node)) {
 | 
			
		||||
            $items[$nid]->search_api_access_node = array();
 | 
			
		||||
          }
 | 
			
		||||
          $items[$nid]->search_api_access_node[] = "node_access_$grant->realm:$grant->gid";
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // Add the generic view grant if we are not using node access or the
 | 
			
		||||
        // node is viewable by anonymous users.
 | 
			
		||||
        $items[$nid]->search_api_access_node = array('node_access__all');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the configuration form.
 | 
			
		||||
   *
 | 
			
		||||
   * If the data alteration is being enabled, set "Published" and "Author" to
 | 
			
		||||
   * "indexed", because both are needed for the node access filter.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $old_status = !empty($form_state['index']->options['data_alter_callbacks']['search_api_alter_node_access']['status']);
 | 
			
		||||
    $new_status = !empty($form_state['values']['callbacks']['search_api_alter_node_access']['status']);
 | 
			
		||||
 | 
			
		||||
    if (!$old_status && $new_status) {
 | 
			
		||||
      $form_state['index']->options['fields']['status']['type'] = 'boolean';
 | 
			
		||||
      $form_state['index']->options['fields']['author']['type'] = 'integer';
 | 
			
		||||
      $form_state['index']->options['fields']['author']['entity_type'] = 'user';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::configurationFormSubmit($form, $values, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								includes/callback_node_status.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								includes/callback_node_status.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Contains the SearchApiAlterNodeStatus class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Exclude unpublished nodes from node indexes.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this data-alter callback is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * Returns TRUE only for indexes on nodes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the callback can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return $index->item_type === 'node';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Alter items before indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Items which are removed from the array won't be indexed, but will be marked
 | 
			
		||||
   * as clean for future indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered, keyed by item IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function alterItems(array &$items) {
 | 
			
		||||
    foreach ($items as $nid => &$item) {
 | 
			
		||||
      if (empty($item->status)) {
 | 
			
		||||
        unset($items[$nid]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										698
									
								
								includes/datasource.inc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										698
									
								
								includes/datasource.inc
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,698 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Contains the SearchApiDataSourceControllerInterface as well as a default base class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for all data source controllers for Search API indexes.
 | 
			
		||||
 *
 | 
			
		||||
 * Data source controllers encapsulate all operations specific to an item type.
 | 
			
		||||
 * They are used for loading items, extracting item data, keeping track of the
 | 
			
		||||
 * item status, etc.
 | 
			
		||||
 *
 | 
			
		||||
 * All methods of the data source may throw exceptions of type
 | 
			
		||||
 * SearchApiDataSourceException if any exception or error state is encountered.
 | 
			
		||||
 */
 | 
			
		||||
interface SearchApiDataSourceControllerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for a data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $type
 | 
			
		||||
   *   The item type for which this controller is created.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($type);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return information on the ID field for this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the following keys:
 | 
			
		||||
   *   - key: The property key for the ID field, as used in the item wrapper.
 | 
			
		||||
   *   - type: The type of the ID field. Has to be one of the types from
 | 
			
		||||
   *     search_api_field_types(). List types ("list<*>") are not allowed.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIdFieldInfo();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Load items of the type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $ids
 | 
			
		||||
   *   The IDs of the items to laod.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The loaded items, keyed by ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function loadItems(array $ids);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a metadata wrapper for the item type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   Unless NULL, an item of the item type for this controller to be wrapped.
 | 
			
		||||
   * @param array $info
 | 
			
		||||
   *   Optionally, additional information that should be used for creating the
 | 
			
		||||
   *   wrapper. Uses the same format as entity_metadata_wrapper().
 | 
			
		||||
   *
 | 
			
		||||
   * @return EntityMetadataWrapper
 | 
			
		||||
   *   A wrapper for the item type of this data source controller, according to
 | 
			
		||||
   *   the info array, and optionally loaded with the given data.
 | 
			
		||||
   *
 | 
			
		||||
   * @see entity_metadata_wrapper()
 | 
			
		||||
   */
 | 
			
		||||
  public function getMetadataWrapper($item = NULL, array $info = array());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the unique ID of an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either the unique ID of the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemId($item);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a human-readable label for an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either a human-readable label for the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemLabel($item);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a URL at which the item can be viewed on the web.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either an array containing the 'path' and 'options' keys used to build
 | 
			
		||||
   *   the URL of the item, and matching the signature of url(), or NULL if the
 | 
			
		||||
   *   item has no URL of its own.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemUrl($item);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initialize tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * All currently known items of this data source's type should be inserted
 | 
			
		||||
   * into the tracking table for the given indexes, with status "changed". If
 | 
			
		||||
   * items were already present, these should also be set to "changed" and not
 | 
			
		||||
   * be inserted again.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be initialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function startTracking(array $indexes);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * The tracking tables of the given indexes should be completely cleared.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be stopped.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function stopTracking(array $indexes);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Start tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of new items to track.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which items should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemInsert(array $item_ids, array $indexes);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "changed"/"dirty".
 | 
			
		||||
   *
 | 
			
		||||
   * Unless $dequeue is set to TRUE, this operation is ignored for items whose
 | 
			
		||||
   * status is not "indexed".
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item_ids
 | 
			
		||||
   *   Either an array with the IDs of the changed items. Or FALSE to mark all
 | 
			
		||||
   *   items as changed for the given indexes.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the change should be tracked.
 | 
			
		||||
   * @param $dequeue
 | 
			
		||||
   *   If set to TRUE, also change the status of queued items.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "queued".
 | 
			
		||||
   *
 | 
			
		||||
   * Queued items are not marked as "dirty" even when they are changed, and they
 | 
			
		||||
   * are not returned by the getChangedItems() method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item_ids
 | 
			
		||||
   *   Either an array with the IDs of the queued items. Or FALSE to mark all
 | 
			
		||||
   *   items as queued for the given indexes.
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which the items were queued.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemQueued($item_ids, SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "indexed".
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the indexed items.
 | 
			
		||||
   * @param SearchApiIndex $indexes
 | 
			
		||||
   *   The index on which the items were indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemIndexed(array $item_ids, SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the removed items.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the deletions should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemDelete(array $item_ids, array $indexes);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a list of items that need to be indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * If possible, completely unindexed items should be returned before items
 | 
			
		||||
   * that were indexed but later changed. Also, items that were changed longer
 | 
			
		||||
   * ago should be favored.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which changed items should be returned.
 | 
			
		||||
   * @param $limit
 | 
			
		||||
   *   The maximum number of items to return. Negative values mean "unlimited".
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The IDs of items that need to be indexed for the given index.
 | 
			
		||||
   */
 | 
			
		||||
  public function getChangedItems(SearchApiIndex $index, $limit = -1);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get information on how many items have been indexed for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose index status should be returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing two keys (in this order):
 | 
			
		||||
   *   - indexed: The number of items already indexed in their latest version.
 | 
			
		||||
   *   - total: The total number of items that have to be indexed for this
 | 
			
		||||
   *     index.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIndexStatus(SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Default base class for the SearchApiDataSourceControllerInterface.
 | 
			
		||||
 *
 | 
			
		||||
 * Contains default implementations for a number of methods which will be
 | 
			
		||||
 * similar for most data sources. Concrete data sources can decide to extend
 | 
			
		||||
 * this base class to save time, but can also implement the interface directly.
 | 
			
		||||
 *
 | 
			
		||||
 * A subclass will still have to provide implementations for the following
 | 
			
		||||
 * methods:
 | 
			
		||||
 * - getIdFieldInfo()
 | 
			
		||||
 * - loadItems()
 | 
			
		||||
 * - getMetadataWrapper() or getPropertyInfo()
 | 
			
		||||
 * - startTracking() or getAllItemIds()
 | 
			
		||||
 *
 | 
			
		||||
 * The table used by default for tracking the index status of items is
 | 
			
		||||
 * {search_api_item}. This can easily be changed, for example when an item type
 | 
			
		||||
 * has non-integer IDs, by changing the $table property.
 | 
			
		||||
 */
 | 
			
		||||
abstract class SearchApiAbstractDataSourceController implements SearchApiDataSourceControllerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The item type for this controller instance.
 | 
			
		||||
   */
 | 
			
		||||
  protected $type;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The info array for the item type, as specified via
 | 
			
		||||
   * hook_search_api_item_type_info().
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $info;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The table used for tracking items. Set to NULL on subclasses to disable
 | 
			
		||||
   * the default tracking for an item type, or change the property to use a
 | 
			
		||||
   * different table for tracking.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $table = 'search_api_item';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * When using the default tracking mechanism: the name of the column on
 | 
			
		||||
   * $this->table containing the item ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $itemIdColumn = 'item_id';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * When using the default tracking mechanism: the name of the column on
 | 
			
		||||
   * $this->table containing the index ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $indexIdColumn = 'index_id';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * When using the default tracking mechanism: the name of the column on
 | 
			
		||||
   * $this->table containing the indexing status.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $changedColumn = 'changed';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for a data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $type
 | 
			
		||||
   *   The item type for which this controller is created.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($type) {
 | 
			
		||||
    $this->type = $type;
 | 
			
		||||
    $this->info = search_api_get_item_type_info($type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a metadata wrapper for the item type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   Unless NULL, an item of the item type for this controller to be wrapped.
 | 
			
		||||
   * @param array $info
 | 
			
		||||
   *   Optionally, additional information that should be used for creating the
 | 
			
		||||
   *   wrapper. Uses the same format as entity_metadata_wrapper().
 | 
			
		||||
   *
 | 
			
		||||
   * @return EntityMetadataWrapper
 | 
			
		||||
   *   A wrapper for the item type of this data source controller, according to
 | 
			
		||||
   *   the info array, and optionally loaded with the given data.
 | 
			
		||||
   *
 | 
			
		||||
   * @see entity_metadata_wrapper()
 | 
			
		||||
   */
 | 
			
		||||
  public function getMetadataWrapper($item = NULL, array $info = array()) {
 | 
			
		||||
    $info += $this->getPropertyInfo();
 | 
			
		||||
    return entity_metadata_wrapper($this->type, $item, $info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method that can be used by subclasses to specify the property
 | 
			
		||||
   * information to use when creating a metadata wrapper.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Property information as specified by hook_entity_property_info().
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPropertyInfo() {
 | 
			
		||||
    throw new SearchApiDataSourceException(t('No known property information for type @type.', array('@type' => $this->type)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the unique ID of an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either the unique ID of the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemId($item) {
 | 
			
		||||
    $id_info = $this->getIdFieldInfo();
 | 
			
		||||
    $field = $id_info['key'];
 | 
			
		||||
    $wrapper = $this->getMetadataWrapper($item);
 | 
			
		||||
    if (!isset($wrapper->$field)) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
    $id = $wrapper->$field->value();
 | 
			
		||||
    return $id ? $id : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a human-readable label for an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either a human-readable label for the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemLabel($item) {
 | 
			
		||||
    $label = $this->getMetadataWrapper($item)->label();
 | 
			
		||||
    return $label ? $label : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a URL at which the item can be viewed on the web.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either an array containing the 'path' and 'options' keys used to build
 | 
			
		||||
   *   the URL of the item, and matching the signature of url(), or NULL if the
 | 
			
		||||
   *   item has no URL of its own.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemUrl($item) {
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initialize tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * All currently known items of this data source's type should be inserted
 | 
			
		||||
   * into the tracking table for the given indexes, with status "changed". If
 | 
			
		||||
   * items were already present, these should also be set to "changed" and not
 | 
			
		||||
   * be inserted again.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be initialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function startTracking(array $indexes) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // We first clear the tracking table for all indexes, so we can just insert
 | 
			
		||||
    // all items again without any key conflicts.
 | 
			
		||||
    $this->stopTracking($indexes);
 | 
			
		||||
    // Insert all items as new.
 | 
			
		||||
    $this->trackItemInsert($this->getAllItemIds(), $indexes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method that can be used by subclasses instead of implementing startTracking().
 | 
			
		||||
   *
 | 
			
		||||
   * Returns the IDs of all items that are known for this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing all item IDs for this type.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAllItemIds() {
 | 
			
		||||
    throw new SearchApiDataSourceException(t('Items not known for type @type.', array('@type' => $this->type)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * The tracking tables of the given indexes should be completely cleared.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be stopped.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function stopTracking(array $indexes) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // We could also use a single query with "IN" operator, but this method
 | 
			
		||||
    // will mostly be called with only one index.
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $this->checkIndex($index);
 | 
			
		||||
      $query = db_delete($this->table)
 | 
			
		||||
        ->condition($this->indexIdColumn, $index->id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Start tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of new items to track.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which items should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemInsert(array $item_ids, array $indexes) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Since large amounts of items can overstrain the database, only add items
 | 
			
		||||
    // in chunks.
 | 
			
		||||
    foreach (array_chunk($item_ids, 1000) as $chunk) {
 | 
			
		||||
      $insert = db_insert($this->table)
 | 
			
		||||
        ->fields(array($this->itemIdColumn, $this->indexIdColumn, $this->changedColumn));
 | 
			
		||||
      foreach ($chunk as $item_id) {
 | 
			
		||||
        foreach ($indexes as $index) {
 | 
			
		||||
          $this->checkIndex($index);
 | 
			
		||||
          $insert->values(array(
 | 
			
		||||
            $this->itemIdColumn => $item_id,
 | 
			
		||||
            $this->indexIdColumn => $index->id,
 | 
			
		||||
            $this->changedColumn => 1,
 | 
			
		||||
          ));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $insert->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "changed"/"dirty".
 | 
			
		||||
   *
 | 
			
		||||
   * Unless $dequeue is set to TRUE, this operation is ignored for items whose
 | 
			
		||||
   * status is not "indexed".
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item_ids
 | 
			
		||||
   *   Either an array with the IDs of the changed items. Or FALSE to mark all
 | 
			
		||||
   *   items as changed for the given indexes.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the change should be tracked.
 | 
			
		||||
   * @param $dequeue
 | 
			
		||||
   *   If set to TRUE, also change the status of queued items.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $index_ids = array();
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $this->checkIndex($index);
 | 
			
		||||
      $index_ids[] = $index->id;
 | 
			
		||||
    }
 | 
			
		||||
    $update = db_update($this->table)
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        $this->changedColumn => REQUEST_TIME,
 | 
			
		||||
      ))
 | 
			
		||||
      ->condition($this->indexIdColumn, $index_ids, 'IN')
 | 
			
		||||
      ->condition($this->changedColumn, 0, $dequeue ? '<=' : '=');
 | 
			
		||||
    if ($item_ids !== FALSE) {
 | 
			
		||||
      $update->condition($this->itemIdColumn, $item_ids, 'IN');
 | 
			
		||||
    }
 | 
			
		||||
    $update->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "queued".
 | 
			
		||||
   *
 | 
			
		||||
   * Queued items are not marked as "dirty" even when they are changed, and they
 | 
			
		||||
   * are not returned by the getChangedItems() method.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item_ids
 | 
			
		||||
   *   Either an array with the IDs of the queued items. Or FALSE to mark all
 | 
			
		||||
   *   items as queued for the given indexes.
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which the items were queued.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemQueued($item_ids, SearchApiIndex $index) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $update = db_update($this->table)
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        $this->changedColumn => -1,
 | 
			
		||||
      ))
 | 
			
		||||
      ->condition($this->indexIdColumn, $index->id);
 | 
			
		||||
    if ($item_ids !== FALSE) {
 | 
			
		||||
      $update->condition($this->itemIdColumn, $item_ids, 'IN');
 | 
			
		||||
    }
 | 
			
		||||
    $update->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "indexed".
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the indexed items.
 | 
			
		||||
   * @param SearchApiIndex $indexes
 | 
			
		||||
   *   The index on which the items were indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemIndexed(array $item_ids, SearchApiIndex $index) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $this->checkIndex($index);
 | 
			
		||||
    db_update($this->table)
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        $this->changedColumn => 0,
 | 
			
		||||
      ))
 | 
			
		||||
      ->condition($this->itemIdColumn, $item_ids, 'IN')
 | 
			
		||||
      ->condition($this->indexIdColumn, $index->id)
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the removed items.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the deletions should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemDelete(array $item_ids, array $indexes) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $index_ids = array();
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $this->checkIndex($index);
 | 
			
		||||
      $index_ids[] = $index->id;
 | 
			
		||||
    }
 | 
			
		||||
    db_delete($this->table)
 | 
			
		||||
      ->condition($this->itemIdColumn, $item_ids, 'IN')
 | 
			
		||||
      ->condition($this->indexIdColumn, $index_ids, 'IN')
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a list of items that need to be indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * If possible, completely unindexed items should be returned before items
 | 
			
		||||
   * that were indexed but later changed. Also, items that were changed longer
 | 
			
		||||
   * ago should be favored.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which changed items should be returned.
 | 
			
		||||
   * @param $limit
 | 
			
		||||
   *   The maximum number of items to return. Negative values mean "unlimited".
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The IDs of items that need to be indexed for the given index.
 | 
			
		||||
   */
 | 
			
		||||
  public function getChangedItems(SearchApiIndex $index, $limit = -1) {
 | 
			
		||||
    if ($limit == 0) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    $this->checkIndex($index);
 | 
			
		||||
    $select = db_select($this->table, 't');
 | 
			
		||||
    $select->addField('t', 'item_id');
 | 
			
		||||
    $select->condition($this->indexIdColumn, $index->id);
 | 
			
		||||
    $select->condition($this->changedColumn, 0, '>');
 | 
			
		||||
    $select->orderBy($this->changedColumn, 'ASC');
 | 
			
		||||
    if ($limit > 0) {
 | 
			
		||||
      $select->range(0, $limit);
 | 
			
		||||
    }
 | 
			
		||||
    return $select->execute()->fetchCol();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get information on how many items have been indexed for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose index status should be returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing two keys (in this order):
 | 
			
		||||
   *   - indexed: The number of items already indexed in their latest version.
 | 
			
		||||
   *   - total: The total number of items that have to be indexed for this
 | 
			
		||||
   *     index.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIndexStatus(SearchApiIndex $index) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return array('indexed' => 0, 'total' => 0);
 | 
			
		||||
    }
 | 
			
		||||
    $this->checkIndex($index);
 | 
			
		||||
    $indexed = db_select($this->table, 'i')
 | 
			
		||||
      ->condition($this->indexIdColumn, $index->id)
 | 
			
		||||
      ->condition($this->changedColumn, 0)
 | 
			
		||||
      ->countQuery()
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetchField();
 | 
			
		||||
    $total = db_select($this->table, 'i')
 | 
			
		||||
      ->condition($this->indexIdColumn, $index->id)
 | 
			
		||||
      ->countQuery()
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetchField();
 | 
			
		||||
    return array('indexed' => $indexed, 'total' => $total);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for ensuring that an index uses the same item type as this controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  protected function checkIndex(SearchApiIndex $index) {
 | 
			
		||||
    if ($index->item_type != $this->type) {
 | 
			
		||||
      $index_type = search_api_get_item_type_info($index->item_type);
 | 
			
		||||
      $index_type = empty($index_type['name']) ? $index->item_type : $index_type['name'];
 | 
			
		||||
      $msg = t('Invalid index @index of type @index_type passed to data source controller for type @this_type.',
 | 
			
		||||
          array('@index' => $index->name, '@index_type' => $index_type, '@this_type' => $this->info['name']));
 | 
			
		||||
      throw new SearchApiDataSourceException($msg);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								includes/datasource_entity.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								includes/datasource_entity.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Contains the SearchApiEntityDataSourceController class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data source for all entities known to the Entity API.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceController {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return information on the ID field for this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the following keys:
 | 
			
		||||
   *   - key: The property key for the ID field, as used in the item wrapper.
 | 
			
		||||
   *   - type: The type of the ID field. Has to be one of the types from
 | 
			
		||||
   *     search_api_field_types(). List types ("list<*>") are not allowed.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIdFieldInfo() {
 | 
			
		||||
    $info = entity_get_info($this->type);
 | 
			
		||||
    $properties = entity_get_property_info($this->type);
 | 
			
		||||
    if (empty($info['entity keys']['id'])) {
 | 
			
		||||
      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $info['label'])));
 | 
			
		||||
    }
 | 
			
		||||
    $field = $info['entity keys']['id'];
 | 
			
		||||
    if (empty($properties['properties'][$field]['type'])) {
 | 
			
		||||
      throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $info['label'], '@prop' => $field)));
 | 
			
		||||
    }
 | 
			
		||||
    $type = $properties['properties'][$field]['type'];
 | 
			
		||||
    if (search_api_is_list_type($type)) {
 | 
			
		||||
      throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $info['label'], '@prop' => $field)));
 | 
			
		||||
    }
 | 
			
		||||
    if ($type == 'token') {
 | 
			
		||||
      $type = 'string';
 | 
			
		||||
    }
 | 
			
		||||
    return array(
 | 
			
		||||
      'key' => $field,
 | 
			
		||||
      'type' => $type,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Load items of the type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $ids
 | 
			
		||||
   *   The IDs of the items to laod.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The loaded items, keyed by ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function loadItems(array $ids) {
 | 
			
		||||
    $items = entity_load($this->type, $ids);
 | 
			
		||||
    // If some items couldn't be loaded, remove them from tracking.
 | 
			
		||||
    if (count($items) != count($ids)) {
 | 
			
		||||
      $ids = array_flip($ids);
 | 
			
		||||
      $unknown = array_keys(array_diff_key($ids, $items));
 | 
			
		||||
      if ($unknown) {
 | 
			
		||||
        search_api_track_item_delete($this->type, $unknown);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $items;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a metadata wrapper for the item type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   Unless NULL, an item of the item type for this controller to be wrapped.
 | 
			
		||||
   * @param array $info
 | 
			
		||||
   *   Optionally, additional information that should be used for creating the
 | 
			
		||||
   *   wrapper. Uses the same format as entity_metadata_wrapper().
 | 
			
		||||
   *
 | 
			
		||||
   * @return EntityMetadataWrapper
 | 
			
		||||
   *   A wrapper for the item type of this data source controller, according to
 | 
			
		||||
   *   the info array, and optionally loaded with the given data.
 | 
			
		||||
   *
 | 
			
		||||
   * @see entity_metadata_wrapper()
 | 
			
		||||
   */
 | 
			
		||||
  public function getMetadataWrapper($item = NULL, array $info = array()) {
 | 
			
		||||
    return entity_metadata_wrapper($this->type, $item, $info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the unique ID of an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either the unique ID of the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemId($item) {
 | 
			
		||||
    $id = entity_id($this->type, $item);
 | 
			
		||||
    return $id ? $id : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a human-readable label for an item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either a human-readable label for the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemLabel($item) {
 | 
			
		||||
    $label = entity_label($this->type, $item);
 | 
			
		||||
    return $label ? $label : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a URL at which the item can be viewed on the web.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either an array containing the 'path' and 'options' keys used to build
 | 
			
		||||
   *   the URL of the item, and matching the signature of url(), or NULL if the
 | 
			
		||||
   *   item has no URL of its own.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemUrl($item) {
 | 
			
		||||
    if ($this->type == 'file') {
 | 
			
		||||
      return array(
 | 
			
		||||
        'path' => file_create_url($item->uri),
 | 
			
		||||
        'options' => array(
 | 
			
		||||
          'entity_type' => 'file',
 | 
			
		||||
          'entity' => $item,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $url = entity_uri($this->type, $item);
 | 
			
		||||
    return $url ? $url : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initialize tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * All currently known items of this data source's type should be inserted
 | 
			
		||||
   * into the tracking table for the given indexes, with status "changed". If
 | 
			
		||||
   * items were already present, these should also be set to "changed" and not
 | 
			
		||||
   * be inserted again.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be initialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function startTracking(array $indexes) {
 | 
			
		||||
    if (!$this->table) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // We first clear the tracking table for all indexes, so we can just insert
 | 
			
		||||
    // all items again without any key conflicts.
 | 
			
		||||
    $this->stopTracking($indexes);
 | 
			
		||||
 | 
			
		||||
    $entity_info = entity_get_info($this->type);
 | 
			
		||||
 | 
			
		||||
    if (!empty($entity_info['base table'])) {
 | 
			
		||||
      // Use a subselect, which will probably be much faster than entity_load().
 | 
			
		||||
 | 
			
		||||
      // Assumes that all entities use the "base table" property and the
 | 
			
		||||
      // "entity keys[id]" in the same way as the default controller.
 | 
			
		||||
      $id_field = $entity_info['entity keys']['id'];
 | 
			
		||||
      $table = $entity_info['base table'];
 | 
			
		||||
 | 
			
		||||
      // We could also use a single insert (with a JOIN in the nested query),
 | 
			
		||||
      // but this method will be mostly called with a single index, anyways.
 | 
			
		||||
      foreach ($indexes as $index) {
 | 
			
		||||
        // Select all entity ids.
 | 
			
		||||
        $query = db_select($table, 't');
 | 
			
		||||
        $query->addField('t', $id_field, 'item_id');
 | 
			
		||||
        $query->addExpression(':index_id', 'index_id', array(':index_id' => $index->id));
 | 
			
		||||
        $query->addExpression('1', 'changed');
 | 
			
		||||
 | 
			
		||||
        // INSERT ... SELECT ...
 | 
			
		||||
        db_insert($this->table)
 | 
			
		||||
          ->from($query)
 | 
			
		||||
          ->execute();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // In the absence of a 'base table', use the slow entity_load().
 | 
			
		||||
      parent::startTracking($indexes);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method that can be used by subclasses instead of implementing startTracking().
 | 
			
		||||
   *
 | 
			
		||||
   * Returns the IDs of all items that are known for this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * Will be used when the entity type doesn't specify a "base table".
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing all item IDs for this type.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAllItemIds() {
 | 
			
		||||
    return array_keys(entity_load($this->type));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										268
									
								
								includes/datasource_external.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								includes/datasource_external.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,268 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Contains the SearchApiExternalDataSourceController class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class for data source controllers for external data sources.
 | 
			
		||||
 *
 | 
			
		||||
 * This data source controller is a base implementation for item types that
 | 
			
		||||
 * represent external data, not directly accessible in Drupal. You can use this
 | 
			
		||||
 * controller as a base class when you don't want to index items of the type via
 | 
			
		||||
 * Drupal, but only want the search capabilities of the Search API. In addition
 | 
			
		||||
 * you most probably also have to create a fitting service class for executing
 | 
			
		||||
 * the actual searches.
 | 
			
		||||
 *
 | 
			
		||||
 * To use most of the functionality of the Search API and related modules, you
 | 
			
		||||
 * will only have to specify some property information in getPropertyInfo(). If
 | 
			
		||||
 * you have a custom service class which already returns the extracted fields
 | 
			
		||||
 * with the search results, you will only have to provide a label and a type for
 | 
			
		||||
 * each field. To make this use case easier, there is also a
 | 
			
		||||
 * getFieldInformation() method which you can implement instead of directly
 | 
			
		||||
 * implementing getPropertyInfo().
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiExternalDataSourceController extends SearchApiAbstractDataSourceController {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return information on the ID field for this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * This implementation will return a field named "id" of type "string". This
 | 
			
		||||
   * can also be used if the item type in question has no IDs.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the following keys:
 | 
			
		||||
   *   - key: The property key for the ID field, as used in the item wrapper.
 | 
			
		||||
   *   - type: The type of the ID field. Has to be one of the types from
 | 
			
		||||
   *     search_api_field_types(). List types ("list<*>") are not allowed.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIdFieldInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'key' => 'id',
 | 
			
		||||
      'type' => 'string',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Load items of the type of this data source controller.
 | 
			
		||||
   *
 | 
			
		||||
   * Always returns an empty array. If you want the items of your type to be
 | 
			
		||||
   * loadable, specify a function here.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $ids
 | 
			
		||||
   *   The IDs of the items to laod.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The loaded items, keyed by ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function loadItems(array $ids) {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method that can be used by subclasses to specify the property
 | 
			
		||||
   * information to use when creating a metadata wrapper.
 | 
			
		||||
   *
 | 
			
		||||
   * For most use cases, you will have to override this method to provide the
 | 
			
		||||
   * real property information for your item type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Property information as specified by hook_entity_property_info().
 | 
			
		||||
   *
 | 
			
		||||
   * @see hook_entity_property_info()
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPropertyInfo() {
 | 
			
		||||
    $info['property info']['id'] = array(
 | 
			
		||||
      'label' => t('ID'),
 | 
			
		||||
      'type' => 'string',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return $info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the unique ID of an item.
 | 
			
		||||
   *
 | 
			
		||||
   * Always returns 1.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either the unique ID of the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemId($item) {
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a human-readable label for an item.
 | 
			
		||||
   *
 | 
			
		||||
   * Always returns NULL.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either a human-readable label for the item, or NULL if none is available.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemLabel($item) {
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a URL at which the item can be viewed on the web.
 | 
			
		||||
   *
 | 
			
		||||
   * Always returns NULL.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   An item of this controller's type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Either an array containing the 'path' and 'options' keys used to build
 | 
			
		||||
   *   the URL of the item, and matching the signature of url(), or NULL if the
 | 
			
		||||
   *   item has no URL of its own.
 | 
			
		||||
   */
 | 
			
		||||
  public function getItemUrl($item) {
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initialize tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * All currently known items of this data source's type should be inserted
 | 
			
		||||
   * into the tracking table for the given indexes, with status "changed". If
 | 
			
		||||
   * items were already present, these should also be set to "changed" and not
 | 
			
		||||
   * be inserted again.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be initialized.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function startTracking(array $indexes) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking of the index status of items for the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * The tracking tables of the given indexes should be completely cleared.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The SearchApiIndex objects for which item tracking should be stopped.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function stopTracking(array $indexes) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Start tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of new items to track.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which items should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemInsert(array $item_ids, array $indexes) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "changed"/"dirty".
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item_ids
 | 
			
		||||
   *   Either an array with the IDs of the changed items. Or FALSE to mark all
 | 
			
		||||
   *   items as changed for the given indexes.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the change should be tracked.
 | 
			
		||||
   * @param $dequeue
 | 
			
		||||
   *   If set to TRUE, also change the status of queued items.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the tracking status of the given items to "indexed".
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the indexed items.
 | 
			
		||||
   * @param SearchApiIndex $indexes
 | 
			
		||||
   *   The index on which the items were indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemIndexed(array $item_ids, SearchApiIndex $index) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop tracking the index status for the given items on the given indexes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $item_ids
 | 
			
		||||
   *   The IDs of the removed items.
 | 
			
		||||
   * @param array $indexes
 | 
			
		||||
   *   The indexes for which the deletions should be tracked.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If any of the indexes doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function trackItemDelete(array $item_ids, array $indexes) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a list of items that need to be indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * If possible, completely unindexed items should be returned before items
 | 
			
		||||
   * that were indexed but later changed. Also, items that were changed longer
 | 
			
		||||
   * ago should be favored.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which changed items should be returned.
 | 
			
		||||
   * @param $limit
 | 
			
		||||
   *   The maximum number of items to return. Negative values mean "unlimited".
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The IDs of items that need to be indexed for the given index.
 | 
			
		||||
   */
 | 
			
		||||
  public function getChangedItems(SearchApiIndex $index, $limit = -1) {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get information on how many items have been indexed for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index whose index status should be returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing two keys (in this order):
 | 
			
		||||
   *   - indexed: The number of items already indexed in their latest version.
 | 
			
		||||
   *   - total: The total number of items that have to be indexed for this
 | 
			
		||||
   *     index.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiDataSourceException
 | 
			
		||||
   *   If the index doesn't use the same item type as this controller.
 | 
			
		||||
   */
 | 
			
		||||
  public function getIndexStatus(SearchApiIndex $index) {
 | 
			
		||||
    return array(
 | 
			
		||||
      'indexed' => 0,
 | 
			
		||||
      'total' => 0,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								includes/exception.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								includes/exception.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an exception or error that occurred in some part of the Search API
 | 
			
		||||
 * framework.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiException extends Exception {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new SearchApiException.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $message
 | 
			
		||||
   *   A string describing the cause of the exception.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($message = NULL) {
 | 
			
		||||
    if (!$message) {
 | 
			
		||||
      $message = t('An error occcurred in the Search API framework.');
 | 
			
		||||
    }
 | 
			
		||||
    parent::__construct($message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an exception that occurred in a data source controller.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiDataSourceException extends SearchApiException {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										936
									
								
								includes/index_entity.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										936
									
								
								includes/index_entity.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,936 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class representing a search index.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiIndex extends Entity {
 | 
			
		||||
 | 
			
		||||
  // Cache values, set when the corresponding methods are called for the first
 | 
			
		||||
  // time.
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cached return value of datasource().
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiDataSourceControllerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $datasource = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cached return value of server().
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiServer
 | 
			
		||||
   */
 | 
			
		||||
  protected $server_object = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * All enabled data alterations for this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $callbacks = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * All enabled processors for this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $processors = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The properties added by data alterations on this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $added_properties = NULL;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array containing two arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * At index 0, all fulltext fields of this index. At index 1, all indexed
 | 
			
		||||
   * fulltext fields of this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $fulltext_fields = array();
 | 
			
		||||
 | 
			
		||||
  // Database values that will be set when object is loaded.
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An integer identifying the index.
 | 
			
		||||
   * Immutable.
 | 
			
		||||
   *
 | 
			
		||||
   * @var integer
 | 
			
		||||
   */
 | 
			
		||||
  public $id;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A name to be displayed for the index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $name;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The machine name of the index.
 | 
			
		||||
   * Immutable.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $machine_name;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A string describing the index' use to users.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $description;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The machine_name of the server with which data should be indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $server;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The type of items stored in this index.
 | 
			
		||||
   * Immutable.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $item_type;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of options for configuring this index. The layout is as follows:
 | 
			
		||||
   * - cron_limit: The maximum number of items to be indexed per cron batch.
 | 
			
		||||
   * - index_directly: Boolean setting whether entities are indexed immediately
 | 
			
		||||
   *   after they are created or updated.
 | 
			
		||||
   * - fields: An array of all indexed fields for this index. Keys are the field
 | 
			
		||||
   *   identifiers, the values are arrays for specifying the field settings. The
 | 
			
		||||
   *   structure of those arrays looks like this:
 | 
			
		||||
   *   - type: The type set for this field. One of the types returned by
 | 
			
		||||
   *     search_api_default_field_types().
 | 
			
		||||
   *   - real_type: (optional) If a custom data type was selected for this
 | 
			
		||||
   *     field, this type will be stored here, and "type" contain the fallback
 | 
			
		||||
   *     default data type.
 | 
			
		||||
   *   - boost: (optional) A boost value for terms found in this field during
 | 
			
		||||
   *     searches. Usually only relevant for fulltext fields. Defaults to 1.0.
 | 
			
		||||
   *   - entity_type (optional): If set, the type of this field is really an
 | 
			
		||||
   *     entity. The "type" key will then just contain the primitive data type
 | 
			
		||||
   *     of the ID field, meaning that servers will ignore this and merely index
 | 
			
		||||
   *     the entity's ID. Components displaying this field, though, are advised
 | 
			
		||||
   *     to use the entity label instead of the ID.
 | 
			
		||||
   * - additional fields: An associative array with keys and values being the
 | 
			
		||||
   *   field identifiers of related entities whose fields should be displayed.
 | 
			
		||||
   * - data_alter_callbacks: An array of all data alterations available. Keys
 | 
			
		||||
   *   are the alteration identifiers, the values are arrays containing the
 | 
			
		||||
   *   settings for that data alteration. The inner structure looks like this:
 | 
			
		||||
   *   - status: Boolean indicating whether the data alteration is enabled.
 | 
			
		||||
   *   - weight: Used for sorting the data alterations.
 | 
			
		||||
   *   - settings: Alteration-specific settings, configured via the alteration's
 | 
			
		||||
   *     configuration form.
 | 
			
		||||
   * - processors: An array of all processors available for the index. The keys
 | 
			
		||||
   *   are the processor identifiers, the values are arrays containing the
 | 
			
		||||
   *   settings for that processor. The inner structure looks like this:
 | 
			
		||||
   *   - status: Boolean indicating whether the processor is enabled.
 | 
			
		||||
   *   - weight: Used for sorting the processors.
 | 
			
		||||
   *   - settings: Processor-specific settings, configured via the processor's
 | 
			
		||||
   *     configuration form.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  public $options = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A flag indicating whether this index is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @var integer
 | 
			
		||||
   */
 | 
			
		||||
  public $enabled = 1;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A flag indicating whether to write to this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @var integer
 | 
			
		||||
   */
 | 
			
		||||
  public $read_only = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor as a helper to the parent constructor.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $values = array()) {
 | 
			
		||||
    parent::__construct($values, 'search_api_index');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute necessary tasks for a newly created index.
 | 
			
		||||
   */
 | 
			
		||||
  public function postCreate() {
 | 
			
		||||
    if ($this->enabled) {
 | 
			
		||||
      $this->queueItems();
 | 
			
		||||
    }
 | 
			
		||||
    $server = $this->server();
 | 
			
		||||
    if ($server) {
 | 
			
		||||
      // Tell the server about the new index.
 | 
			
		||||
      if ($server->enabled) {
 | 
			
		||||
        $server->addIndex($this);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $tasks = variable_get('search_api_tasks', array());
 | 
			
		||||
        // When we add or remove an index, we can ignore all other tasks.
 | 
			
		||||
        $tasks[$server->machine_name][$this->machine_name] = array('add');
 | 
			
		||||
        variable_set('search_api_tasks', $tasks);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute necessary tasks when the index is removed from the database.
 | 
			
		||||
   */
 | 
			
		||||
  public function postDelete() {
 | 
			
		||||
    if ($server = $this->server()) {
 | 
			
		||||
      if ($server->enabled) {
 | 
			
		||||
        $server->removeIndex($this);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $tasks = variable_get('search_api_tasks', array());
 | 
			
		||||
        $tasks[$server->machine_name][$this->machine_name] = array('remove');
 | 
			
		||||
        variable_set('search_api_tasks', $tasks);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Stop tracking entities for indexing.
 | 
			
		||||
    $this->dequeueItems();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Record entities to index.
 | 
			
		||||
   */
 | 
			
		||||
  public function queueItems() {
 | 
			
		||||
    if (!$this->read_only) {
 | 
			
		||||
      $this->datasource()->startTracking(array($this));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove all records of entities to index.
 | 
			
		||||
   */
 | 
			
		||||
  public function dequeueItems() {
 | 
			
		||||
    $this->datasource()->stopTracking(array($this));
 | 
			
		||||
    _search_api_empty_cron_queue($this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Saves this index to the database, either creating a new record or updating
 | 
			
		||||
   * an existing one.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   Failure to save the index will return FALSE. Otherwise, SAVED_NEW or
 | 
			
		||||
   *   SAVED_UPDATED is returned depending on the operation performed. $this->id
 | 
			
		||||
   *   will be set if a new index was inserted.
 | 
			
		||||
   */
 | 
			
		||||
  public function save() {
 | 
			
		||||
    if (empty($this->description)) {
 | 
			
		||||
      $this->description = NULL;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($this->server)) {
 | 
			
		||||
      $this->server = NULL;
 | 
			
		||||
      $this->enabled = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    // This will also throw an exception if the server doesn't exist – which is good.
 | 
			
		||||
    elseif (!$this->server(TRUE)->enabled) {
 | 
			
		||||
      $this->enabled = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for updating entity properties.
 | 
			
		||||
   *
 | 
			
		||||
   * NOTE: You shouldn't change any properties of this object before calling
 | 
			
		||||
   * this method, as this might lead to the fields not being saved correctly.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $fields
 | 
			
		||||
   *   The new field values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   SAVE_UPDATED on success, FALSE on failure, 0 if the fields already had
 | 
			
		||||
   *   the specified values.
 | 
			
		||||
   */
 | 
			
		||||
  public function update(array $fields) {
 | 
			
		||||
    $changeable = array('name' => 1, 'enabled' => 1, 'description' => 1, 'server' => 1, 'options' => 1, 'read_only' => 1);
 | 
			
		||||
    $changed = FALSE;
 | 
			
		||||
    foreach ($fields as $field => $value) {
 | 
			
		||||
      if (isset($changeable[$field]) && $value !== $this->$field) {
 | 
			
		||||
        $this->$field = $value;
 | 
			
		||||
        $changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If there are no new values, just return 0.
 | 
			
		||||
    if (!$changed) {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Reset the index's internal property cache to correctly incorporate new
 | 
			
		||||
    // settings.
 | 
			
		||||
    $this->resetCaches();
 | 
			
		||||
 | 
			
		||||
    return $this->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Schedules this search index for re-indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE on success, FALSE on failure.
 | 
			
		||||
   */
 | 
			
		||||
  public function reindex() {
 | 
			
		||||
    if (!$this->server || $this->read_only) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    _search_api_index_reindex($this);
 | 
			
		||||
    module_invoke_all('search_api_index_reindex', $this, FALSE);
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clears this search index and schedules all of its items for re-indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE on success, FALSE on failure.
 | 
			
		||||
   */
 | 
			
		||||
  public function clear() {
 | 
			
		||||
    if (!$this->server || $this->read_only) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $server = $this->server();
 | 
			
		||||
    if ($server->enabled) {
 | 
			
		||||
      $server->deleteItems('all', $this);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $tasks = variable_get('search_api_tasks', array());
 | 
			
		||||
      // If the index was cleared or newly added since the server was last enabled, we don't need to do anything.
 | 
			
		||||
      if (!isset($tasks[$server->machine_name][$this->machine_name])
 | 
			
		||||
          || (array_search('add', $tasks[$server->machine_name][$this->machine_name]) === FALSE
 | 
			
		||||
              && array_search('clear', $tasks[$server->machine_name][$this->machine_name]) === FALSE)) {
 | 
			
		||||
        $tasks[$server->machine_name][$this->machine_name][] = 'clear';
 | 
			
		||||
        variable_set('search_api_tasks', $tasks);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _search_api_index_reindex($this);
 | 
			
		||||
    module_invoke_all('search_api_index_reindex', $this, TRUE);
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Magic method for determining which fields should be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * Don't serialize properties that are basically only caches.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of properties to be serialized.
 | 
			
		||||
   */
 | 
			
		||||
  public function __sleep() {
 | 
			
		||||
    $ret = get_object_vars($this);
 | 
			
		||||
    unset($ret['server_object'], $ret['datasource'], $ret['processors'], $ret['added_properties'], $ret['fulltext_fields']);
 | 
			
		||||
    return array_keys($ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the controller object of the data source used by this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If the specified item type or data source doesn't exist or is invalid.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiDataSourceControllerInterface
 | 
			
		||||
   *   The data source controller for this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function datasource() {
 | 
			
		||||
    if (!isset($this->datasource)) {
 | 
			
		||||
      $this->datasource = search_api_get_datasource_controller($this->item_type);
 | 
			
		||||
    }
 | 
			
		||||
    return $this->datasource;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the server this index lies on.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $reset
 | 
			
		||||
   *   Whether to reset the internal cache. Set to TRUE when the index' $server
 | 
			
		||||
   *   property has just changed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If $this->server is set, but no server with that machine name exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiServer
 | 
			
		||||
   *   The server associated with this index, or NULL if this index currently
 | 
			
		||||
   *   doesn't lie on a server.
 | 
			
		||||
   */
 | 
			
		||||
  public function server($reset = FALSE) {
 | 
			
		||||
    if (!isset($this->server_object) || $reset) {
 | 
			
		||||
      $this->server_object = $this->server ? search_api_server_load($this->server) : FALSE;
 | 
			
		||||
      if ($this->server && !$this->server_object) {
 | 
			
		||||
        throw new SearchApiException(t('Unknown server @server specified for index @name.', array('@server' => $this->server, '@name' => $this->machine_name)));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->server_object ? $this->server_object : NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a query object for this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $options
 | 
			
		||||
   *   Associative array of options configuring this query. See
 | 
			
		||||
   *   SearchApiQueryInterface::__construct().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If the index is currently disabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiQueryInterface
 | 
			
		||||
   *   A query object for searching this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function query($options = array()) {
 | 
			
		||||
    if (!$this->enabled) {
 | 
			
		||||
      throw new SearchApiException(t('Cannot search on a disabled index.'));
 | 
			
		||||
    }
 | 
			
		||||
    return $this->server()->query($this, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indexes items on this index. Will return an array of IDs of items that
 | 
			
		||||
   * should be marked as indexed – i.e., items that were either rejected by a
 | 
			
		||||
   * data-alter callback or were successfully indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to index.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of the IDs of all items that should be marked as indexed.
 | 
			
		||||
   */
 | 
			
		||||
  public function index(array $items) {
 | 
			
		||||
    if ($this->read_only) {
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
    if (!$this->enabled) {
 | 
			
		||||
      throw new SearchApiException(t("Couldn't index values on '@name' index (index is disabled)", array('@name' => $this->name)));
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($this->options['fields'])) {
 | 
			
		||||
      throw new SearchApiException(t("Couldn't index values on '@name' index (no fields selected)", array('@name' => $this->name)));
 | 
			
		||||
    }
 | 
			
		||||
    $fields = $this->options['fields'];
 | 
			
		||||
    $custom_type_fields = array();
 | 
			
		||||
    foreach ($fields as $field => $info) {
 | 
			
		||||
      if (isset($info['real_type'])) {
 | 
			
		||||
        $custom_type = search_api_extract_inner_type($info['real_type']);
 | 
			
		||||
        if ($this->server()->supportsFeature('search_api_data_type_' . $custom_type)) {
 | 
			
		||||
          $fields[$field]['type'] = $info['real_type'];
 | 
			
		||||
          $custom_type_fields[$custom_type][$field] = search_api_list_nesting_level($info['real_type']);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($fields)) {
 | 
			
		||||
      throw new SearchApiException(t("Couldn't index values on '@name' index (no fields selected)", array('@name' => $this->name)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mark all items that are rejected as indexed.
 | 
			
		||||
    $ret = array_keys($items);
 | 
			
		||||
    drupal_alter('search_api_index_items', $items, $this);
 | 
			
		||||
    if ($items) {
 | 
			
		||||
      $this->dataAlter($items);
 | 
			
		||||
    }
 | 
			
		||||
    $ret = array_diff($ret, array_keys($items));
 | 
			
		||||
 | 
			
		||||
    // Items that are rejected should also be deleted from the server.
 | 
			
		||||
    if ($ret) {
 | 
			
		||||
      $this->server()->deleteItems($ret, $this);
 | 
			
		||||
    }
 | 
			
		||||
    if (!$items) {
 | 
			
		||||
      return $ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $data = array();
 | 
			
		||||
    foreach ($items as $id => $item) {
 | 
			
		||||
      $data[$id] = search_api_extract_fields($this->entityWrapper($item), $fields);
 | 
			
		||||
      unset($items[$id]);
 | 
			
		||||
      foreach ($custom_type_fields as $type => $type_fields) {
 | 
			
		||||
        $info = search_api_get_data_type_info($type);
 | 
			
		||||
        if (isset($info['conversion callback']) && is_callable($info['conversion callback'])) {
 | 
			
		||||
          $callback = $info['conversion callback'];
 | 
			
		||||
          foreach ($type_fields as $field => $nesting_level) {
 | 
			
		||||
            if (isset($data[$id][$field]['value'])) {
 | 
			
		||||
              $value = $data[$id][$field]['value'];
 | 
			
		||||
              $original_type = $data[$id][$field]['original_type'];
 | 
			
		||||
              $data[$id][$field]['value'] = _search_api_convert_custom_type($callback, $value, $original_type, $type, $nesting_level);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->preprocessIndexItems($data);
 | 
			
		||||
 | 
			
		||||
    return array_merge($ret, $this->server()->indexItems($this, $data));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calls data alteration hooks for a set of items, according to the index
 | 
			
		||||
   * options.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be altered.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiIndex
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function dataAlter(array &$items) {
 | 
			
		||||
    // First, execute our own search_api_language data alteration.
 | 
			
		||||
    foreach ($items as &$item) {
 | 
			
		||||
      $item->search_api_language = isset($item->language) ? $item->language : LANGUAGE_NONE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getAlterCallbacks() as $callback) {
 | 
			
		||||
      $callback->alterItems($items);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Property info alter callback that adds the infos of the properties added by
 | 
			
		||||
   * data alter callbacks.
 | 
			
		||||
   *
 | 
			
		||||
   * @param EntityMetadataWrapper $wrapper
 | 
			
		||||
   *   The wrapped data.
 | 
			
		||||
   * @param $property_info
 | 
			
		||||
   *   The original property info.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The altered property info.
 | 
			
		||||
   */
 | 
			
		||||
  public function propertyInfoAlter(EntityMetadataWrapper $wrapper, array $property_info) {
 | 
			
		||||
    if (entity_get_property_info($wrapper->type())) {
 | 
			
		||||
      // Overwrite the existing properties with the list of properties including
 | 
			
		||||
      // all fields regardless of the used bundle.
 | 
			
		||||
      $property_info['properties'] = entity_get_all_property_info($wrapper->type());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isset($this->added_properties)) {
 | 
			
		||||
      $this->added_properties = array(
 | 
			
		||||
        'search_api_language' => array(
 | 
			
		||||
          'label' => t('Item language'),
 | 
			
		||||
          'description' => t("A field added by the search framework to let components determine an item's language. Is always indexed."),
 | 
			
		||||
          'type' => 'token',
 | 
			
		||||
          'options list' => 'entity_metadata_language_list',
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      // We use the reverse order here so the hierarchy for overwriting property infos is the same
 | 
			
		||||
      // as for actually overwriting the properties.
 | 
			
		||||
      foreach (array_reverse($this->getAlterCallbacks()) as $callback) {
 | 
			
		||||
        $props = $callback->propertyInfo();
 | 
			
		||||
        if ($props) {
 | 
			
		||||
          $this->added_properties += $props;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Let fields added by data-alter callbacks override default fields.
 | 
			
		||||
    $property_info['properties'] = array_merge($property_info['properties'], $this->added_properties);
 | 
			
		||||
 | 
			
		||||
    return $property_info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
   * Fills the $processors array for use by the pre-/postprocessing functions.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiIndex
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   All enabled callbacks for this index, as SearchApiAlterCallbackInterface
 | 
			
		||||
   *   objects.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAlterCallbacks() {
 | 
			
		||||
    if (isset($this->callbacks)) {
 | 
			
		||||
      return $this->callbacks;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->callbacks = array();
 | 
			
		||||
    if (empty($this->options['data_alter_callbacks'])) {
 | 
			
		||||
      return $this->callbacks;
 | 
			
		||||
    }
 | 
			
		||||
    $callback_settings = $this->options['data_alter_callbacks'];
 | 
			
		||||
    $infos = search_api_get_alter_callbacks();
 | 
			
		||||
 | 
			
		||||
    foreach ($callback_settings as $id => $settings) {
 | 
			
		||||
      if (empty($settings['status'])) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (empty($infos[$id]) || !class_exists($infos[$id]['class'])) {
 | 
			
		||||
        watchdog('search_api', t('Undefined data alteration @class specified in index @name', array('@class' => $id, '@name' => $this->name)), NULL, WATCHDOG_WARNING);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $class = $infos[$id]['class'];
 | 
			
		||||
      $callback = new $class($this, empty($settings['settings']) ? array() : $settings['settings']);
 | 
			
		||||
      if (!($callback instanceof SearchApiAlterCallbackInterface)) {
 | 
			
		||||
        watchdog('search_api', t('Unknown callback class @class specified for data alteration @name', array('@class' => $class, '@name' => $id)), NULL, WATCHDOG_WARNING);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $this->callbacks[$id] = $callback;
 | 
			
		||||
    }
 | 
			
		||||
    return $this->callbacks;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   All enabled processors for this index, as SearchApiProcessorInterface
 | 
			
		||||
   *   objects.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getProcessors() {
 | 
			
		||||
    if (isset($this->processors)) {
 | 
			
		||||
      return $this->processors;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->processors = array();
 | 
			
		||||
    if (empty($this->options['processors'])) {
 | 
			
		||||
      return $this->processors;
 | 
			
		||||
    }
 | 
			
		||||
    $processor_settings = $this->options['processors'];
 | 
			
		||||
    $infos = search_api_get_processors();
 | 
			
		||||
 | 
			
		||||
    foreach ($processor_settings as $id => $settings) {
 | 
			
		||||
      if (empty($settings['status'])) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (empty($infos[$id]) || !class_exists($infos[$id]['class'])) {
 | 
			
		||||
        watchdog('search_api', t('Undefined processor @class specified in index @name', array('@class' => $id, '@name' => $this->name)), NULL, WATCHDOG_WARNING);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $class = $infos[$id]['class'];
 | 
			
		||||
      $processor = new $class($this, isset($settings['settings']) ? $settings['settings'] : array());
 | 
			
		||||
      if (!($processor instanceof SearchApiProcessorInterface)) {
 | 
			
		||||
        watchdog('search_api', t('Unknown processor class @class specified for processor @name', array('@class' => $class, '@name' => $id)), NULL, WATCHDOG_WARNING);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $this->processors[$id] = $processor;
 | 
			
		||||
    }
 | 
			
		||||
    return $this->processors;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Preprocess data items for indexing. Data added by data alter callbacks will
 | 
			
		||||
   * be available on the items.
 | 
			
		||||
   *
 | 
			
		||||
   * Typically, a preprocessor will execute its preprocessing (e.g. stemming,
 | 
			
		||||
   * n-grams, word splitting, stripping stop words, etc.) only on the items'
 | 
			
		||||
   * fulltext fields. Other fields should usually be left untouched.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be preprocessed for indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiIndex
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessIndexItems(array &$items) {
 | 
			
		||||
    foreach ($this->getProcessors() as $processor) {
 | 
			
		||||
      $processor->preprocessIndexItems($items);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Preprocess a search query.
 | 
			
		||||
   *
 | 
			
		||||
   * The same applies as when preprocessing indexed items: typically, only the
 | 
			
		||||
   * fulltext search keys should be processed, queries on specific fields should
 | 
			
		||||
   * usually not be altered.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiQuery $query
 | 
			
		||||
   *   The object representing the query to be executed.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiIndex
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessSearchQuery(SearchApiQuery $query) {
 | 
			
		||||
    foreach ($this->getProcessors() as $processor) {
 | 
			
		||||
      $processor->preprocessSearchQuery($query);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Postprocess search results before display.
 | 
			
		||||
   *
 | 
			
		||||
   * If a class is used for both pre- and post-processing a search query, the
 | 
			
		||||
   * same object will be used for both calls (so preserving some data or state
 | 
			
		||||
   * locally is possible).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $response
 | 
			
		||||
   *   An array containing the search results. See
 | 
			
		||||
   *   SearchApiServiceInterface->search() for the detailed format.
 | 
			
		||||
   * @param SearchApiQuery $query
 | 
			
		||||
   *   The object representing the executed query.
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiIndex
 | 
			
		||||
   *   The called object.
 | 
			
		||||
   */
 | 
			
		||||
  public function postprocessSearchResults(array &$response, SearchApiQuery $query) {
 | 
			
		||||
    // Postprocessing is done in exactly the opposite direction than preprocessing.
 | 
			
		||||
    foreach (array_reverse($this->getProcessors()) as $processor) {
 | 
			
		||||
      $processor->postprocessSearchResults($response, $query);
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a list of all known fields for this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $only_indexed (optional)
 | 
			
		||||
   *   Return only indexed fields, not all known fields. Defaults to TRUE.
 | 
			
		||||
   * @param $get_additional (optional)
 | 
			
		||||
   *   Return not only known/indexed fields, but also related entities whose
 | 
			
		||||
   *   fields could additionally be added to the index.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of all known fields for this index. Keys are the field
 | 
			
		||||
   *   identifiers, the values are arrays for specifying the field settings. The
 | 
			
		||||
   *   structure of those arrays looks like this:
 | 
			
		||||
   *   - name: The human-readable name for the field.
 | 
			
		||||
   *   - description: A description of the field, if available.
 | 
			
		||||
   *   - indexed: Boolean indicating whether the field is indexed or not.
 | 
			
		||||
   *   - type: The type set for this field. One of the types returned by
 | 
			
		||||
   *     search_api_default_field_types().
 | 
			
		||||
   *   - real_type: (optional) If a custom data type was selected for this
 | 
			
		||||
   *     field, this type will be stored here, and "type" contain the fallback
 | 
			
		||||
   *     default data type.
 | 
			
		||||
   *   - boost: A boost value for terms found in this field during searches.
 | 
			
		||||
   *     Usually only relevant for fulltext fields.
 | 
			
		||||
   *   - entity_type (optional): If set, the type of this field is really an
 | 
			
		||||
   *     entity. The "type" key will then contain "integer", meaning that
 | 
			
		||||
   *     servers will ignore this and merely index the entity's ID. Components
 | 
			
		||||
   *     displaying this field, though, are advised to use the entity label
 | 
			
		||||
   *     instead of the ID.
 | 
			
		||||
   *   If $get_additional is TRUE, this array is encapsulated in another
 | 
			
		||||
   *   associative array, which contains the above array under the "fields" key,
 | 
			
		||||
   *   and a list of related entities (field keys mapped to names) under the
 | 
			
		||||
   *   "additional fields" key.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFields($only_indexed = TRUE, $get_additional = FALSE) {
 | 
			
		||||
    $fields = empty($this->options['fields']) ? array() : $this->options['fields'];
 | 
			
		||||
    $wrapper = $this->entityWrapper();
 | 
			
		||||
    $additional = array();
 | 
			
		||||
    $entity_types = entity_get_info();
 | 
			
		||||
 | 
			
		||||
    // First we need all already added prefixes.
 | 
			
		||||
    $added = ($only_indexed || empty($this->options['additional fields'])) ? array() : $this->options['additional fields'];
 | 
			
		||||
    foreach (array_keys($fields) as $key) {
 | 
			
		||||
      $len = strlen($key) + 1;
 | 
			
		||||
      $pos = $len;
 | 
			
		||||
      // The third parameter ($offset) to strrpos has rather weird behaviour,
 | 
			
		||||
      // necessitating this rather awkward code. It will iterate over all
 | 
			
		||||
      // prefixes of each field, beginning with the longest, adding all of them
 | 
			
		||||
      // to $added until one is encountered that was already added (which means
 | 
			
		||||
      // all shorter ones will have already been added, too).
 | 
			
		||||
      while ($pos = strrpos($key, ':', $pos - $len)) {
 | 
			
		||||
        $prefix = substr($key, 0, $pos);
 | 
			
		||||
        if (isset($added[$prefix])) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        $added[$prefix] = $prefix;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Then we walk through all properties and look if they are already
 | 
			
		||||
    // contained in one of the arrays.
 | 
			
		||||
    // Since this uses an iterative instead of a recursive approach, it is a bit
 | 
			
		||||
    // complicated, with three arrays tracking the current depth.
 | 
			
		||||
 | 
			
		||||
    // A wrapper for a specific field name prefix, e.g. 'user:' mapped to the user wrapper
 | 
			
		||||
    $wrappers = array('' => $wrapper);
 | 
			
		||||
    // Display names for the prefixes
 | 
			
		||||
    $prefix_names = array('' => '');
 | 
			
		||||
      // The list nesting level for entities with a certain prefix
 | 
			
		||||
    $nesting_levels = array('' => 0);
 | 
			
		||||
 | 
			
		||||
    $types = search_api_default_field_types();
 | 
			
		||||
    $flat = array();
 | 
			
		||||
    while ($wrappers) {
 | 
			
		||||
      foreach ($wrappers as $prefix => $wrapper) {
 | 
			
		||||
        $prefix_name = $prefix_names[$prefix];
 | 
			
		||||
        // Deal with lists of entities.
 | 
			
		||||
        $nesting_level = $nesting_levels[$prefix];
 | 
			
		||||
        $type_prefix = str_repeat('list<', $nesting_level);
 | 
			
		||||
        $type_suffix = str_repeat('>', $nesting_level);
 | 
			
		||||
        if ($nesting_level) {
 | 
			
		||||
          $info = $wrapper->info();
 | 
			
		||||
          // The real nesting level of the wrapper, not the accumulated one.
 | 
			
		||||
          $level = search_api_list_nesting_level($info['type']);
 | 
			
		||||
          for ($i = 0; $i < $level; ++$i) {
 | 
			
		||||
            $wrapper = $wrapper[0];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // Now look at all properties.
 | 
			
		||||
        foreach ($wrapper as $property => $value) {
 | 
			
		||||
          $info = $value->info();
 | 
			
		||||
          // We hide the complexity of multi-valued types from the user here.
 | 
			
		||||
          $type = search_api_extract_inner_type($info['type']);
 | 
			
		||||
          // Treat Entity API type "token" as our "string" type.
 | 
			
		||||
          // Also let text fields with limited options be of type "string" by default.
 | 
			
		||||
          if ($type == 'token' || ($type == 'text' && !empty($info['options list']))) {
 | 
			
		||||
            // Inner type is changed to "string".
 | 
			
		||||
            $type = 'string';
 | 
			
		||||
            // Set the field type accordingly.
 | 
			
		||||
            $info['type'] = search_api_nest_type('string', $info['type']);
 | 
			
		||||
          }
 | 
			
		||||
          $info['type'] = $type_prefix . $info['type'] . $type_suffix;
 | 
			
		||||
          $key = $prefix . $property;
 | 
			
		||||
          if ((isset($types[$type]) || isset($entity_types[$type])) && (!$only_indexed || !empty($fields[$key]))) {
 | 
			
		||||
            if (!empty($fields[$key])) {
 | 
			
		||||
              // This field is already known in the index configuration.
 | 
			
		||||
              $flat[$key] = $fields[$key] + array(
 | 
			
		||||
                'name' => $prefix_name . $info['label'],
 | 
			
		||||
                'description' => empty($info['description']) ? NULL : $info['description'],
 | 
			
		||||
                'boost' => '1.0',
 | 
			
		||||
                'indexed' => TRUE,
 | 
			
		||||
              );
 | 
			
		||||
              // Update the type and its nesting level for non-entity properties.
 | 
			
		||||
              if (!isset($entity_types[$type])) {
 | 
			
		||||
                $flat[$key]['type'] = search_api_nest_type(search_api_extract_inner_type($flat[$key]['type']), $info['type']);
 | 
			
		||||
                if (isset($flat[$key]['real_type'])) {
 | 
			
		||||
                  $real_type = search_api_extract_inner_type($flat[$key]['real_type']);
 | 
			
		||||
                  $flat[$key]['real_type'] = search_api_nest_type($real_type, $info['type']);
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              $flat[$key] = array(
 | 
			
		||||
                'name'    => $prefix_name . $info['label'],
 | 
			
		||||
                'description' => empty($info['description']) ? NULL : $info['description'],
 | 
			
		||||
                'type'    => $info['type'],
 | 
			
		||||
                'boost' => '1.0',
 | 
			
		||||
                'indexed' => FALSE,
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            if (isset($entity_types[$type])) {
 | 
			
		||||
              $base_type = isset($entity_types[$type]['entity keys']['name']) ? 'string' : 'integer';
 | 
			
		||||
              $flat[$key]['type'] = search_api_nest_type($base_type, $info['type']);
 | 
			
		||||
              $flat[$key]['entity_type'] = $type;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (empty($types[$type])) {
 | 
			
		||||
            if (isset($added[$key])) {
 | 
			
		||||
              // Visit this entity/struct in a later iteration.
 | 
			
		||||
              $wrappers[$key . ':'] = $value;
 | 
			
		||||
              $prefix_names[$key . ':'] = $prefix_name . $info['label'] . ' » ';
 | 
			
		||||
              $nesting_levels[$key . ':'] = search_api_list_nesting_level($info['type']);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              $name = $prefix_name . $info['label'];
 | 
			
		||||
              // Add machine names to discern fields with identical labels.
 | 
			
		||||
              if (isset($used_names[$name])) {
 | 
			
		||||
                if ($used_names[$name] !== FALSE) {
 | 
			
		||||
                  $additional[$used_names[$name]] .= ' [' . $used_names[$name] . ']';
 | 
			
		||||
                  $used_names[$name] = FALSE;
 | 
			
		||||
                }
 | 
			
		||||
                $name .= ' [' . $key . ']';
 | 
			
		||||
              }
 | 
			
		||||
              $additional[$key] = $name;
 | 
			
		||||
              $used_names[$name] = $key;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        unset($wrappers[$prefix]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!$get_additional) {
 | 
			
		||||
      return $flat;
 | 
			
		||||
    }
 | 
			
		||||
    $options = array();
 | 
			
		||||
    $options['fields'] = $flat;
 | 
			
		||||
    $options['additional fields'] = $additional;
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Convenience method for getting all of this index's fulltext fields.
 | 
			
		||||
   *
 | 
			
		||||
   * @param boolean $only_indexed
 | 
			
		||||
   *   If set to TRUE, only the indexed fulltext fields will be returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing all (or all indexed) fulltext fields defined for this
 | 
			
		||||
   *   index.
 | 
			
		||||
   */
 | 
			
		||||
  public function getFulltextFields($only_indexed = TRUE) {
 | 
			
		||||
    $i = $only_indexed ? 1 : 0;
 | 
			
		||||
    if (!isset($this->fulltext_fields[$i])) {
 | 
			
		||||
      $this->fulltext_fields[$i] = array();
 | 
			
		||||
      $fields = $only_indexed ? $this->options['fields'] : $this->getFields(FALSE);
 | 
			
		||||
      foreach ($fields as $key => $field) {
 | 
			
		||||
        if (search_api_is_text_type($field['type'])) {
 | 
			
		||||
          $this->fulltext_fields[$i][] = $key;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->fulltext_fields[$i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper function for creating an entity metadata wrapper appropriate for
 | 
			
		||||
   * this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $item
 | 
			
		||||
   *   Unless NULL, an item of this index's item type which should be wrapped.
 | 
			
		||||
   * @param $alter
 | 
			
		||||
   *   Whether to apply the index's active data alterations on the property
 | 
			
		||||
   *   information used. To also apply the data alteration to the wrapped item,
 | 
			
		||||
   *   execute SearchApiIndex::dataAlter() on it before calling this method.
 | 
			
		||||
   *
 | 
			
		||||
   * @return EntityMetadataWrapper
 | 
			
		||||
   *   A wrapper for the item type of this index, optionally loaded with the
 | 
			
		||||
   *   given data and having additional fields according to the data alterations
 | 
			
		||||
   *   of this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function entityWrapper($item = NULL, $alter = TRUE) {
 | 
			
		||||
    $info['property info alter'] = $alter ? array($this, 'propertyInfoAlter') : '_search_api_wrapper_add_all_properties';
 | 
			
		||||
    $info['property defaults']['property info alter'] = '_search_api_wrapper_add_all_properties';
 | 
			
		||||
    return $this->datasource()->getMetadataWrapper($item, $info);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method to load items from the type lying on this index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $ids
 | 
			
		||||
   *   The IDs of the items to load.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The requested items, as loaded by the data source.
 | 
			
		||||
   *
 | 
			
		||||
   * @see SearchApiDataSourceControllerInterface::loadItems()
 | 
			
		||||
   */
 | 
			
		||||
  public function loadItems(array $ids) {
 | 
			
		||||
    return $this->datasource()->loadItems($ids);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reset internal static caches.
 | 
			
		||||
   *
 | 
			
		||||
   * Should be used when things like fields or data alterations change to avoid
 | 
			
		||||
   * using stale data.
 | 
			
		||||
   */
 | 
			
		||||
  public function resetCaches() {
 | 
			
		||||
    $this->datasource = NULL;
 | 
			
		||||
    $this->server_object = NULL;
 | 
			
		||||
    $this->callbacks = NULL;
 | 
			
		||||
    $this->processors = NULL;
 | 
			
		||||
    $this->added_properties = NULL;
 | 
			
		||||
    $this->fulltext_fields = array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										418
									
								
								includes/processor.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										418
									
								
								includes/processor.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,418 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a Search API pre- and/or post-processor.
 | 
			
		||||
 *
 | 
			
		||||
 * While processors are enabled or disabled for both pre- and postprocessing at
 | 
			
		||||
 * once, many processors will only need to run in one of those two phases. Then,
 | 
			
		||||
 * the other method(s) should simply be left blank. A processor should make it
 | 
			
		||||
 * clear in its description or documentation when it will run and what effect it
 | 
			
		||||
 * will have.
 | 
			
		||||
 * Usually, processors preprocessing indexed items will likewise preprocess
 | 
			
		||||
 * search queries, so these two methods should mostly be implemented either both
 | 
			
		||||
 * or neither.
 | 
			
		||||
 */
 | 
			
		||||
interface SearchApiProcessorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Construct a processor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index for which processing is done.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The processor options set for this index.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check whether this processor is applicable for a certain index.
 | 
			
		||||
   *
 | 
			
		||||
   * This can be used for hiding the processor on the index's "Workflow" tab. To
 | 
			
		||||
   * avoid confusion, you should only use criteria that are immutable, such as
 | 
			
		||||
   * the index's item type. Also, since this is only used for UI purposes, you
 | 
			
		||||
   * should not completely rely on this to ensure certain index configurations
 | 
			
		||||
   * and at least throw an exception with a descriptive error message if this is
 | 
			
		||||
   * violated on runtime.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to check for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if the processor can run on the given index; FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display a form for configuring this processor.
 | 
			
		||||
   * Since forcing users to specify options for disabled processors makes no
 | 
			
		||||
   * sense, none of the form elements should have the '#required' attribute set.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for configuring this processor, or FALSE if no configuration
 | 
			
		||||
   *   is possible.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validation callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should both return the new options and set them internally.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The new options array for this callback.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Preprocess data items for indexing.
 | 
			
		||||
   *
 | 
			
		||||
   * Typically, a preprocessor will execute its preprocessing (e.g. stemming,
 | 
			
		||||
   * n-grams, word splitting, stripping stop words, etc.) only on the items'
 | 
			
		||||
   * search_api_fulltext fields, if set. Other fields should usually be left
 | 
			
		||||
   * untouched.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be preprocessed for indexing, formatted as specified
 | 
			
		||||
   *   by SearchApiServiceInterface::indexItems().
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessIndexItems(array &$items);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Preprocess a search query.
 | 
			
		||||
   *
 | 
			
		||||
   * The same applies as when preprocessing indexed items: typically, only the
 | 
			
		||||
   * fulltext search keys should be processed, queries on specific fields should
 | 
			
		||||
   * usually not be altered.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiQuery $query
 | 
			
		||||
   *   The object representing the query to be executed.
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessSearchQuery(SearchApiQuery $query);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Postprocess search results before display.
 | 
			
		||||
   *
 | 
			
		||||
   * If a class is used for both pre- and post-processing a search query, the
 | 
			
		||||
   * same object will be used for both calls (so preserving some data or state
 | 
			
		||||
   * locally is possible).
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $response
 | 
			
		||||
   *   An array containing the search results. See the return value of
 | 
			
		||||
   *   SearchApiQueryInterface->execute() for the detailed format.
 | 
			
		||||
   * @param SearchApiQuery $query
 | 
			
		||||
   *   The object representing the executed query.
 | 
			
		||||
   */
 | 
			
		||||
  public function postprocessSearchResults(array &$response, SearchApiQuery $query);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract processor implementation that provides an easy framework for only
 | 
			
		||||
 * processing specific fields.
 | 
			
		||||
 *
 | 
			
		||||
 * Simple processors can just override process(), while others might want to
 | 
			
		||||
 * override the other process*() methods, and test*() (for restricting
 | 
			
		||||
 * processing to something other than all fulltext data).
 | 
			
		||||
 */
 | 
			
		||||
abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var SearchApiIndex
 | 
			
		||||
   */
 | 
			
		||||
  protected $index;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $options;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor, saving its arguments into properties.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array()) {
 | 
			
		||||
    $this->index   = $index;
 | 
			
		||||
    $this->options = $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function supportsIndex(SearchApiIndex $index) {
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
 | 
			
		||||
 | 
			
		||||
    $fields = $this->index->getFields();
 | 
			
		||||
    $field_options = array();
 | 
			
		||||
    $default_fields = array();
 | 
			
		||||
    if (isset($this->options['fields'])) {
 | 
			
		||||
      $default_fields = drupal_map_assoc(array_keys($this->options['fields']));
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($fields as $name => $field) {
 | 
			
		||||
      $field_options[$name] = $field['name'];
 | 
			
		||||
      if (!empty($default_fields[$name]) || (!isset($this->options['fields']) && $this->testField($name, $field))) {
 | 
			
		||||
        $default_fields[$name] = $name;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form['fields'] = array(
 | 
			
		||||
      '#type' => 'checkboxes',
 | 
			
		||||
      '#title' => t('Fields to run on'),
 | 
			
		||||
      '#options' => $field_options,
 | 
			
		||||
      '#default_value' => $default_fields,
 | 
			
		||||
      '#attributes' => array('class' => array('search-api-checkboxes-list')),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $fields = array_filter($values['fields']);
 | 
			
		||||
    if ($fields) {
 | 
			
		||||
      $fields = array_combine($fields, array_fill(0, count($fields), TRUE));
 | 
			
		||||
    }
 | 
			
		||||
    $values['fields'] = $fields;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $this->options = $values;
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calls processField() for all appropriate fields.
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessIndexItems(array &$items) {
 | 
			
		||||
    foreach ($items as &$item) {
 | 
			
		||||
      foreach ($item as $name => &$field) {
 | 
			
		||||
        if ($this->testField($name, $field)) {
 | 
			
		||||
          $this->processField($field['value'], $field['type']);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calls processKeys() for the keys and processFilters() for the filters.
 | 
			
		||||
   */
 | 
			
		||||
  public function preprocessSearchQuery(SearchApiQuery $query) {
 | 
			
		||||
    $keys = &$query->getKeys();
 | 
			
		||||
    $this->processKeys($keys);
 | 
			
		||||
    $filter = $query->getFilter();
 | 
			
		||||
    $filters = &$filter->getFilters();
 | 
			
		||||
    $this->processFilters($filters);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Does nothing.
 | 
			
		||||
   */
 | 
			
		||||
  public function postprocessSearchResults(array &$response, SearchApiQuery $query) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Method for preprocessing field data.
 | 
			
		||||
   *
 | 
			
		||||
   * Calls process() either for the whole text, or each token, depending on the
 | 
			
		||||
   * type. Also takes care of extracting list values and of fusing returned
 | 
			
		||||
   * tokens back into a one-dimensional array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processField(&$value, &$type) {
 | 
			
		||||
    if (!isset($value) || $value === '') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (substr($type, 0, 5) == 'list<') {
 | 
			
		||||
      $inner_type = $t = $t1 = substr($type, 5, -1);
 | 
			
		||||
      foreach ($value as &$v) {
 | 
			
		||||
        $t1 = $inner_type;
 | 
			
		||||
        $this->processField($v, $t1);
 | 
			
		||||
        // If one value got tokenized, all others have to follow.
 | 
			
		||||
        if ($t1 != $inner_type) {
 | 
			
		||||
          $t = $t1;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ($t == 'tokens') {
 | 
			
		||||
        foreach ($value as $i => &$v) {
 | 
			
		||||
          if (!$v) {
 | 
			
		||||
            unset($value[$i]);
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
          if (!is_array($v)) {
 | 
			
		||||
            $v = array(array('value' => $v, 'score' => 1));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $type = "list<$t>";
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($type == 'tokens') {
 | 
			
		||||
      foreach ($value as &$token) {
 | 
			
		||||
        $this->processFieldValue($token['value']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->processFieldValue($value);
 | 
			
		||||
    }
 | 
			
		||||
    if (is_array($value)) {
 | 
			
		||||
      $type = 'tokens';
 | 
			
		||||
      $value = $this->normalizeTokens($value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Internal helper function for normalizing tokens.
 | 
			
		||||
   */
 | 
			
		||||
  protected function normalizeTokens($tokens, $score = 1) {
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    foreach ($tokens as $token) {
 | 
			
		||||
      if (empty($token['value']) && !is_numeric($token['value'])) {
 | 
			
		||||
        // Filter out empty tokens.
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (!isset($token['score'])) {
 | 
			
		||||
        $token['score'] = $score;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $token['score'] *= $score;
 | 
			
		||||
      }
 | 
			
		||||
      if (is_array($token['value'])) {
 | 
			
		||||
        foreach ($this->normalizeTokens($token['value'], $token['score']) as $t) {
 | 
			
		||||
          $ret[] = $t;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $ret[] = $token;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Method for preprocessing search keys.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processKeys(&$keys) {
 | 
			
		||||
    if (is_array($keys)) {
 | 
			
		||||
      foreach ($keys as $key => &$v) {
 | 
			
		||||
        if (element_child($key)) {
 | 
			
		||||
          $this->processKeys($v);
 | 
			
		||||
          if (!$v && !is_numeric($v)) {
 | 
			
		||||
            unset($keys[$key]);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->processKey($keys);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Method for preprocessing query filters.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processFilters(array &$filters) {
 | 
			
		||||
    $fields = $this->index->options['fields'];
 | 
			
		||||
    foreach ($filters as &$f) {
 | 
			
		||||
      if (is_array($f)) {
 | 
			
		||||
        if (isset($fields[$f[0]]) && $this->testField($f[0], $fields[$f[0]])) {
 | 
			
		||||
          $this->processFilterValue($f[1]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $child_filters = &$f->getFilters();
 | 
			
		||||
        $this->processFilters($child_filters);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param $name
 | 
			
		||||
   *   The field's machine name.
 | 
			
		||||
   * @param array $field
 | 
			
		||||
   *   The field's information.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, iff the field should be processed.
 | 
			
		||||
   */
 | 
			
		||||
  protected function testField($name, array $field) {
 | 
			
		||||
    if (empty($this->options['fields'])) {
 | 
			
		||||
      return $this->testType($field['type']);
 | 
			
		||||
    }
 | 
			
		||||
    return !empty($this->options['fields'][$name]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, iff the type should be processed.
 | 
			
		||||
   */
 | 
			
		||||
  protected function testType($type) {
 | 
			
		||||
    return search_api_is_text_type($type, array('text', 'tokens'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called for processing a single text element in a field. The default
 | 
			
		||||
   * implementation just calls process().
 | 
			
		||||
   *
 | 
			
		||||
   * $value can either be left a string, or changed into an array of tokens. A
 | 
			
		||||
   * token is an associative array containing:
 | 
			
		||||
   * - value: Either the text inside the token, or a nested array of tokens. The
 | 
			
		||||
   *   score of nested tokens will be multiplied by their parent's score.
 | 
			
		||||
   * - score: The relative importance of the token, as a float, with 1 being
 | 
			
		||||
   *   the default.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processFieldValue(&$value) {
 | 
			
		||||
    $this->process($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called for processing a single search keyword. The default implementation
 | 
			
		||||
   * just calls process().
 | 
			
		||||
   *
 | 
			
		||||
   * $value can either be left a string, or be changed into a nested keys array,
 | 
			
		||||
   * as defined by SearchApiQueryInterface.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processKey(&$value) {
 | 
			
		||||
    $this->process($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called for processing a single filter value. The default implementation
 | 
			
		||||
   * just calls process().
 | 
			
		||||
   *
 | 
			
		||||
   * $value has to remain a string.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processFilterValue(&$value) {
 | 
			
		||||
    $this->process($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function that is ultimately called for all text by the standard
 | 
			
		||||
   * implementation, and does nothing by default.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $value
 | 
			
		||||
   *   The value to preprocess as a string. Can be manipulated directly, nothing
 | 
			
		||||
   *   has to be returned. Since this can be called for all value types, $value
 | 
			
		||||
   *   has to remain a string.
 | 
			
		||||
   */
 | 
			
		||||
  protected function process(&$value) {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										137
									
								
								includes/processor_html_filter.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								includes/processor_html_filter.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Processor for stripping HTML from indexed fulltext data. Supports assigning
 | 
			
		||||
 * custom boosts for any HTML element.
 | 
			
		||||
 */
 | 
			
		||||
// @todo Process query?
 | 
			
		||||
class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $tags;
 | 
			
		||||
 | 
			
		||||
  public function __construct(SearchApiIndex $index, array $options = array()) {
 | 
			
		||||
    parent::__construct($index, $options);
 | 
			
		||||
    $this->options += array(
 | 
			
		||||
      'title' => FALSE,
 | 
			
		||||
      'alt'   => TRUE,
 | 
			
		||||
      'tags'  => "h1 = 5\n" .
 | 
			
		||||
          "h2 = 3\n" .
 | 
			
		||||
          "h3 = 2\n" .
 | 
			
		||||
          "strong = 2\n" .
 | 
			
		||||
          "b = 2\n" .
 | 
			
		||||
          "em = 1.5\n" .
 | 
			
		||||
          'u = 1.5',
 | 
			
		||||
    );
 | 
			
		||||
    $this->tags = drupal_parse_info_format($this->options['tags']);
 | 
			
		||||
    // Specifying empty tags doesn't make sense.
 | 
			
		||||
    unset($this->tags['br'], $this->tags['hr']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form = parent::configurationForm();
 | 
			
		||||
    $form += array(
 | 
			
		||||
      'title' => array(
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => t('Index title attribute'),
 | 
			
		||||
        '#description' => t('If set, the contents of title attributes will be indexed.'),
 | 
			
		||||
        '#default_value' => $this->options['title'],
 | 
			
		||||
      ),
 | 
			
		||||
      'alt' => array(
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => t('Index alt attribute'),
 | 
			
		||||
        '#description' => t('If set, the alternative text of images will be indexed.'),
 | 
			
		||||
        '#default_value' => $this->options['alt'],
 | 
			
		||||
      ),
 | 
			
		||||
      'tags' => array(
 | 
			
		||||
        '#type' => 'textarea',
 | 
			
		||||
        '#title' => t('Tag boosts'),
 | 
			
		||||
        '#description' => t('Specify special boost values for certain HTML elements, in <a href="@link">INI file format</a>. ' .
 | 
			
		||||
            'The boost values of nested elements are multiplied, elements not mentioned will have the default boost value of 1. ' .
 | 
			
		||||
            'Assign a boost of 0 to ignore the text content of that HTML element.',
 | 
			
		||||
            array('@link' => url('http://api.drupal.org/api/function/drupal_parse_info_format/7'))),
 | 
			
		||||
        '#default_value' => $this->options['tags'],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    parent::configurationFormValidate($form, $values, $form_state);
 | 
			
		||||
 | 
			
		||||
    if (empty($values['tags'])) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $tags = drupal_parse_info_format($values['tags']);
 | 
			
		||||
    $errors = array();
 | 
			
		||||
    foreach ($tags as $key => $value) {
 | 
			
		||||
      if (is_array($value)) {
 | 
			
		||||
        $errors[] = t("Boost value for tag <@tag> can't be an array.", array('@tag' => $key));
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!is_numeric($value)) {
 | 
			
		||||
        $errors[] = t("Boost value for tag <@tag> must be numeric.", array('@tag' => $key));
 | 
			
		||||
      }
 | 
			
		||||
      elseif ($value < 0) {
 | 
			
		||||
        $errors[] = t('Boost value for tag <@tag> must be non-negative.', array('@tag' => $key));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($errors) {
 | 
			
		||||
      form_error($form['tags'], implode("<br />\n", $errors));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function processFieldValue(&$value) {
 | 
			
		||||
    $text = str_replace(array('<', '>'), array(' <', '> '), $value); // Let removed tags still delimit words.
 | 
			
		||||
    if ($this->options['title']) {
 | 
			
		||||
      $text = preg_replace('/(<[-a-z_]+[^>]+)\btitle\s*=\s*("([^"]+)"|\'([^\']+)\')([^>]*>)/i', '$1 $5 $3$4 ', $text);
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->options['alt']) {
 | 
			
		||||
      $text = preg_replace('/<img\b[^>]+\balt\s*=\s*("([^"]+)"|\'([^\']+)\')[^>]*>/i', ' <img>$2$3</img> ', $text);
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->tags) {
 | 
			
		||||
      $text = strip_tags($text, '<' . implode('><', array_keys($this->tags)) . '>');
 | 
			
		||||
      $value = $this->parseText($text);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $value = strip_tags($text);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function parseText(&$text, $active_tag = NULL, $boost = 1) {
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    while (($pos = strpos($text, '<')) !== FALSE) {
 | 
			
		||||
      if ($boost && $pos > 0) {
 | 
			
		||||
        $ret[] = array(
 | 
			
		||||
          'value' => html_entity_decode(substr($text, 0, $pos), ENT_QUOTES, 'UTF-8'),
 | 
			
		||||
          'score' => $boost,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      $text = substr($text, $pos + 1);
 | 
			
		||||
      preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m);
 | 
			
		||||
      $text = substr($text, strpos($text, '>') + 1);
 | 
			
		||||
      if ($m[1]) {
 | 
			
		||||
        // Closing tag.
 | 
			
		||||
        if ($active_tag && $m[2] == $active_tag) {
 | 
			
		||||
          return $ret;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // Opening tag => recursive call.
 | 
			
		||||
        $inner_boost = $boost * (isset($this->tags[$m[2]]) ? $this->tags[$m[2]] : 1);
 | 
			
		||||
        $ret = array_merge($ret, $this->parseText($text, $m[2], $inner_boost));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($text) {
 | 
			
		||||
      $ret[] = array(
 | 
			
		||||
        'value' => html_entity_decode($text, ENT_QUOTES, 'UTF-8'),
 | 
			
		||||
        'score' => $boost,
 | 
			
		||||
      );
 | 
			
		||||
      $text = '';
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								includes/processor_ignore_case.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								includes/processor_ignore_case.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Processor for making searches case-insensitive.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiIgnoreCase extends SearchApiAbstractProcessor {
 | 
			
		||||
 | 
			
		||||
  protected function process(&$value) {
 | 
			
		||||
    $value = drupal_strtolower($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								includes/processor_stopwords.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								includes/processor_stopwords.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Processor for removing stopwords from index and search terms.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiStopWords extends SearchApiAbstractProcessor {
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form = parent::configurationForm();
 | 
			
		||||
 | 
			
		||||
    $form += array(
 | 
			
		||||
      'help' => array(
 | 
			
		||||
        '#markup' => '<p>' . t('Provide a stopwords file or enter the words in this form. If you do both, both will be used. Read about !stopwords.', array('!stopwords' => l(t('stop words'), "http://en.wikipedia.org/wiki/Stop_words"))) . '</p>',
 | 
			
		||||
      ),
 | 
			
		||||
      'file' => array(
 | 
			
		||||
        '#type' => 'textfield',
 | 
			
		||||
        '#title' => t('Stopwords file URI'),
 | 
			
		||||
        '#title' => t('Enter the URI of your stopwords.txt file'),
 | 
			
		||||
        '#description' => t('This must be a stream-type description like <code>public://stopwords/stopwords.txt</code> or <code>http://example.com/stopwords.txt</code> or <code>private://stopwords.txt</code>.'),
 | 
			
		||||
      ),
 | 
			
		||||
      'stopwords' => array(
 | 
			
		||||
        '#type' => 'textarea',
 | 
			
		||||
        '#title' => t('Stopwords'),
 | 
			
		||||
        '#description' => t('Enter a space and/or linebreak separated list of stopwords that will be removed from content before it is indexed and from search terms before searching.'),
 | 
			
		||||
        '#default_value' => t("but\ndid\nthe this that those\netc"),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (!empty($this->options)) {
 | 
			
		||||
      $form['file']['#default_value'] = $this->options['file'];
 | 
			
		||||
      $form['stopwords']['#default_value'] = $this->options['stopwords'];
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    parent::configurationFormValidate($form, $values, $form_state);
 | 
			
		||||
 | 
			
		||||
    $stopwords = trim($values['stopwords']);
 | 
			
		||||
    $uri = $values['file'];
 | 
			
		||||
    if (empty($stopwords) && empty($uri)) {
 | 
			
		||||
      $el = $form['file'];
 | 
			
		||||
      form_error($el, $el['#title'] . ': ' . t('At stopwords file or words are required.'));
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($uri) && !file_get_contents($uri)) {
 | 
			
		||||
      $el = $form['file'];
 | 
			
		||||
      form_error($el, t('Stopwords file') . ': ' . t('The file %uri is not readable or does not exist.', array('%uri' => $uri)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function process(&$value) {
 | 
			
		||||
    $stopwords = $this->getStopWords();
 | 
			
		||||
    if (empty($stopwords)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    $words = preg_split('/\s+/', $value);
 | 
			
		||||
    foreach ($words as $sub_key => $sub_value) {
 | 
			
		||||
      if (isset($stopwords[$sub_value])) {
 | 
			
		||||
        unset($words[$sub_key]);
 | 
			
		||||
        $this->ignored[] = $sub_value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $value = implode(' ', $words);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function postprocessSearchResults(array &$response, SearchApiQuery $query) {
 | 
			
		||||
    if (isset($this->ignored)) {
 | 
			
		||||
      if (isset($response['ignored'])) {
 | 
			
		||||
        $response['ignored'] = array_merge($response['ignored'], $this->ignored);
 | 
			
		||||
      }
 | 
			
		||||
      else $response['ignored'] = $this->ignored;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return
 | 
			
		||||
   *   An array whose keys are the stopwords set in either the file or the text
 | 
			
		||||
   *   field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getStopWords() {
 | 
			
		||||
    if (isset($this->stopwords)) {
 | 
			
		||||
      return $this->stopwords;
 | 
			
		||||
    }
 | 
			
		||||
    $file_words = $form_words = array();
 | 
			
		||||
    if (!empty($this->options['file']) && $stopwords_file = file_get_contents($this->options['file'])) {
 | 
			
		||||
      $file_words = preg_split('/\s+/', $stopwords_file);
 | 
			
		||||
    }
 | 
			
		||||
    if (!empty($this->options['stopwords'])) {
 | 
			
		||||
      $form_words = preg_split('/\s+/', $this->options['stopwords']);
 | 
			
		||||
    }
 | 
			
		||||
    $this->stopwords = array_flip(array_merge($file_words, $form_words));
 | 
			
		||||
    return $this->stopwords;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								includes/processor_tokenizer.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								includes/processor_tokenizer.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Processor for tokenizing fulltext data by replacing (configurable)
 | 
			
		||||
 * non-letters with spaces.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiTokenizer extends SearchApiAbstractProcessor {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $spaces;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $ignorable;
 | 
			
		||||
 | 
			
		||||
  public function configurationForm() {
 | 
			
		||||
    $form = parent::configurationForm();
 | 
			
		||||
    $form += array(
 | 
			
		||||
      'spaces' => array(
 | 
			
		||||
        '#type' => 'textfield',
 | 
			
		||||
        '#title' => t('Whitespace characters'),
 | 
			
		||||
        '#description' => t('Specify the characters that should be regarded as whitespace and therefore used as word-delimiters. ' .
 | 
			
		||||
            'Specify the characters as a <a href="@link">PCRE character class</a>. ' .
 | 
			
		||||
            'Note: For non-English content, the default setting might not be suitable.',
 | 
			
		||||
            array('@link' => url('http://www.php.net/manual/en/regexp.reference.character-classes.php'))),
 | 
			
		||||
        '#default_value' => "[^[:alnum:]]",
 | 
			
		||||
      ),
 | 
			
		||||
      'ignorable' => array(
 | 
			
		||||
        '#type' => 'textfield',
 | 
			
		||||
        '#title' => t('Ignorable characters'),
 | 
			
		||||
        '#description' => t('Specify characters which should be removed from fulltext fields and search strings (e.g., "-"). The same format as above is used.'),
 | 
			
		||||
        '#default_value' => "[']",
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (!empty($this->options)) {
 | 
			
		||||
      $form['spaces']['#default_value']   = $this->options['spaces'];
 | 
			
		||||
      $form['ignorable']['#default_value'] = $this->options['ignorable'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    parent::configurationFormValidate($form, $values, $form_state);
 | 
			
		||||
 | 
			
		||||
    $spaces = str_replace('/', '\/', $values['spaces']);
 | 
			
		||||
    $ignorable = str_replace('/', '\/', $values['ignorable']);
 | 
			
		||||
    if (@preg_match('/(' . $spaces . ')+/u', '') === FALSE) {
 | 
			
		||||
      $el = $form['spaces'];
 | 
			
		||||
      form_error($el, $el['#title'] . ': ' . t('The entered text is no valid regular expression.'));
 | 
			
		||||
    }
 | 
			
		||||
    if (@preg_match('/(' . $ignorable . ')+/u', '') === FALSE) {
 | 
			
		||||
      $el = $form['ignorable'];
 | 
			
		||||
      form_error($el, $el['#title'] . ': ' . t('The entered text is no valid regular expression.'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function processFieldValue(&$value) {
 | 
			
		||||
    $this->prepare();
 | 
			
		||||
    if ($this->ignorable) {
 | 
			
		||||
      $value = preg_replace('/(' . $this->ignorable . ')+/u', '', $value);
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->spaces) {
 | 
			
		||||
      $arr = preg_split('/(' . $this->spaces . ')+/u', $value);
 | 
			
		||||
      if (count($arr) > 1) {
 | 
			
		||||
        $value = array();
 | 
			
		||||
        foreach ($arr as $token) {
 | 
			
		||||
          $value[] = array('value' => $token);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function process(&$value) {
 | 
			
		||||
    $this->prepare();
 | 
			
		||||
    if ($this->ignorable) {
 | 
			
		||||
      $value = preg_replace('/' . $this->ignorable . '+/u', '', $value);
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->spaces) {
 | 
			
		||||
      $value = preg_replace('/' . $this->spaces . '+/u', ' ', $value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function prepare() {
 | 
			
		||||
    if (!isset($this->spaces)) {
 | 
			
		||||
      $this->spaces = str_replace('/', '\/', $this->options['spaces']);
 | 
			
		||||
      $this->ignorable = str_replace('/', '\/', $this->options['ignorable']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1066
									
								
								includes/query.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1066
									
								
								includes/query.inc
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										228
									
								
								includes/server_entity.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								includes/server_entity.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class representing a search server.
 | 
			
		||||
 *
 | 
			
		||||
 * This can handle the same calls as defined in the SearchApiServiceInterface
 | 
			
		||||
 * and pass it on to the service implementation appropriate for this server.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiServer extends Entity {
 | 
			
		||||
 | 
			
		||||
  /* Database values that will be set when object is loaded: */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The primary identifier for a server.
 | 
			
		||||
   *
 | 
			
		||||
   * @var integer
 | 
			
		||||
   */
 | 
			
		||||
  public $id = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The displayed name for a server.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $name = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The machine name for a server.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $machine_name = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The displayed description for a server.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $description = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The id of the service class to use for this server.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  public $class = '';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The options used to configure the service object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  public $options = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A flag indicating whether the server is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @var integer
 | 
			
		||||
   */
 | 
			
		||||
  public $enabled = 1;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Proxy object for invoking service methods.
 | 
			
		||||
   *
 | 
			
		||||
   * @var SearchApiServiceInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $proxy;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor as a helper to the parent constructor.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $values = array()) {
 | 
			
		||||
    parent::__construct($values, 'search_api_server');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for updating entity properties.
 | 
			
		||||
   *
 | 
			
		||||
   * NOTE: You shouldn't change any properties of this object before calling
 | 
			
		||||
   * this method, as this might lead to the fields not being saved correctly.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $fields
 | 
			
		||||
   *   The new field values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   SAVE_UPDATED on success, FALSE on failure, 0 if the fields already had
 | 
			
		||||
   *   the specified values.
 | 
			
		||||
   */
 | 
			
		||||
  public function update(array $fields) {
 | 
			
		||||
    $changeable = array('name' => 1, 'enabled' => 1, 'description' => 1, 'options' => 1);
 | 
			
		||||
    $changed = FALSE;
 | 
			
		||||
    foreach ($fields as $field => $value) {
 | 
			
		||||
      if (isset($changeable[$field]) && $value !== $this->$field) {
 | 
			
		||||
        $this->$field = $value;
 | 
			
		||||
        $changed = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // If there are no new values, just return 0.
 | 
			
		||||
    if (!$changed) {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    return $this->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Magic method for determining which fields should be serialized.
 | 
			
		||||
   *
 | 
			
		||||
   * Serialize all properties except the proxy object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of properties to be serialized.
 | 
			
		||||
   */
 | 
			
		||||
  public function __sleep() {
 | 
			
		||||
    $ret = get_object_vars($this);
 | 
			
		||||
    unset($ret['proxy'], $ret['status'], $ret['module'], $ret['is_new']);
 | 
			
		||||
    return array_keys($ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for ensuring the proxy object is set up.
 | 
			
		||||
   */
 | 
			
		||||
  protected function ensureProxy() {
 | 
			
		||||
    if (!isset($this->proxy)) {
 | 
			
		||||
      $class = search_api_get_service_info($this->class);
 | 
			
		||||
      if ($class && class_exists($class['class'])) {
 | 
			
		||||
        if (empty($this->options)) {
 | 
			
		||||
          // We always have to provide the options.
 | 
			
		||||
          $this->options = array();
 | 
			
		||||
        }
 | 
			
		||||
        $this->proxy = new $class['class']($this);
 | 
			
		||||
      }
 | 
			
		||||
      if (!($this->proxy instanceof SearchApiServiceInterface)) {
 | 
			
		||||
        throw new SearchApiException(t('Search server with machine name @name specifies illegal service class @class.', array('@name' => $this->machine_name, '@class' => $this->class)));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If the service class defines additional methods, not specified in the
 | 
			
		||||
   * SearchApiServiceInterface interface, then they are called via this magic
 | 
			
		||||
   * method.
 | 
			
		||||
   */
 | 
			
		||||
  public function __call($name, $arguments = array()) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return call_user_func_array(array($this->proxy, $name), $arguments);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Proxy methods
 | 
			
		||||
 | 
			
		||||
  // For increased clarity, and since some parameters are passed by reference,
 | 
			
		||||
  // we don't use the __call() magic method for those.
 | 
			
		||||
 | 
			
		||||
  public function configurationForm(array $form, array &$form_state) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->configurationForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->configurationFormValidate($form, $values, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->configurationFormSubmit($form, $values, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function supportsFeature($feature) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->supportsFeature($feature);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function viewSettings() {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->viewSettings();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function postCreate() {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->postCreate();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function postUpdate() {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->postUpdate();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function preDelete() {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->preDelete();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function addIndex(SearchApiIndex $index) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->addIndex($index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function fieldsUpdated(SearchApiIndex $index) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->fieldsUpdated($index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function removeIndex($index) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->removeIndex($index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function indexItems(SearchApiIndex $index, array $items) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->indexItems($index, $items);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->deleteItems($ids, $index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function query(SearchApiIndex $index, $options = array()) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->query($index, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function search(SearchApiQueryInterface $query) {
 | 
			
		||||
    $this->ensureProxy();
 | 
			
		||||
    return $this->proxy->search($query);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										469
									
								
								includes/service.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										469
									
								
								includes/service.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,469 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface defining the methods search services have to implement.
 | 
			
		||||
 *
 | 
			
		||||
 * Before a service object is used, the corresponding server's data will be read
 | 
			
		||||
 * from the database (see SearchApiAbstractService for a list of fields).
 | 
			
		||||
 */
 | 
			
		||||
interface SearchApiServiceInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for a service class, setting the server configuration used with
 | 
			
		||||
   * this service.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiServer $server
 | 
			
		||||
   *   The server object for this service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiServer $server);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form callback. Might be called on an uninitialized object - in this case,
 | 
			
		||||
   * the form is for configuring a newly created server.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for setting service-specific options.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm(array $form, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validation callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * $form_state['server'] will contain the server that is created or edited.
 | 
			
		||||
   * Use form_error() to flag errors on form elements.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * This method should set the options of this service' server according to
 | 
			
		||||
   * $values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether this service class implementation supports a given
 | 
			
		||||
   * feature. Features are optional extensions to Search API functionality and
 | 
			
		||||
   * usually defined and used by third-party modules.
 | 
			
		||||
   *
 | 
			
		||||
   * There are currently three features defined directly in the Search API
 | 
			
		||||
   * project:
 | 
			
		||||
   * - "search_api_facets", by the search_api_facetapi module.
 | 
			
		||||
   * - "search_api_facets_operator_or", also by the search_api_facetapi module.
 | 
			
		||||
   * - "search_api_mlt", by the search_api_views module.
 | 
			
		||||
   * Other contrib modules might define additional features. These should always
 | 
			
		||||
   * be properly documented in the module by which they are defined.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $feature
 | 
			
		||||
   *   The name of the optional feature.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if this service knows and supports the specified feature. FALSE
 | 
			
		||||
   *   otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsFeature($feature);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * View this server's settings. Output can be HTML or a render array, a <dl>
 | 
			
		||||
   * listing all relevant settings is preferred.
 | 
			
		||||
   */
 | 
			
		||||
  public function viewSettings();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called once, when the server is first created. Allows it to set up its
 | 
			
		||||
   * necessary infrastructure.
 | 
			
		||||
   */
 | 
			
		||||
  public function postCreate();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies this server that its fields are about to be updated. The server's
 | 
			
		||||
   * $original property can be used to inspect the old property values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, if the update requires reindexing of all content on the server.
 | 
			
		||||
   */
 | 
			
		||||
  public function postUpdate();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies this server that it is about to be deleted from the database and
 | 
			
		||||
   * should therefore clean up, if appropriate.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that you shouldn't call the server's save() method, or any
 | 
			
		||||
   * methods that might do that, from inside of this method as the server isn't
 | 
			
		||||
   * present in the database anymore at this point.
 | 
			
		||||
   */
 | 
			
		||||
  public function preDelete();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a new index to this server.
 | 
			
		||||
   *
 | 
			
		||||
   * If the index was already added to the server, the object should treat this
 | 
			
		||||
   * as if removeIndex() and then addIndex() were called.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to add.
 | 
			
		||||
   */
 | 
			
		||||
  public function addIndex(SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notify the server that the indexed field settings for the index have
 | 
			
		||||
   * changed.
 | 
			
		||||
   * If any user action is necessary as a result of this, the method should
 | 
			
		||||
   * use drupal_set_message() to notify the user.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The updated index.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, if this change affected the server in any way that forces it to
 | 
			
		||||
   *   re-index the content. FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function fieldsUpdated(SearchApiIndex $index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove an index from this server.
 | 
			
		||||
   *
 | 
			
		||||
   * This might mean that the index has been deleted, or reassigned to a
 | 
			
		||||
   * different server. If you need to distinguish between these cases, inspect
 | 
			
		||||
   * $index->server.
 | 
			
		||||
   *
 | 
			
		||||
   * If the index wasn't added to the server, the method call should be ignored.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $index
 | 
			
		||||
   *   Either an object representing the index to remove, or its machine name
 | 
			
		||||
   *   (if the index was completely deleted).
 | 
			
		||||
   */
 | 
			
		||||
  public function removeIndex($index);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Index the specified items.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The search index for which items should be indexed.
 | 
			
		||||
   * @param array $items
 | 
			
		||||
   *   An array of items to be indexed, keyed by their id. The values are
 | 
			
		||||
   *   associative arrays of the fields to be stored, where each field is an
 | 
			
		||||
   *   array with the following keys:
 | 
			
		||||
   *   - type: One of the data types recognized by the Search API, or the
 | 
			
		||||
   *     special type "tokens" for fulltext fields.
 | 
			
		||||
   *   - original_type: The original type of the property, as defined by the
 | 
			
		||||
   *     datasource controller for the index's item type.
 | 
			
		||||
   *   - value: The value to index.
 | 
			
		||||
   *
 | 
			
		||||
   *   The special field "search_api_language" contains the item's language and
 | 
			
		||||
   *   should always be indexed.
 | 
			
		||||
   *
 | 
			
		||||
   *   The value of fields with the "tokens" type is an array of tokens. Each
 | 
			
		||||
   *   token is an array containing the following keys:
 | 
			
		||||
   *   - value: The word that the token represents.
 | 
			
		||||
   *   - score: A score for the importance of that word.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of the ids of all items that were successfully indexed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If indexing was prevented by a fundamental configuration error.
 | 
			
		||||
   */
 | 
			
		||||
  public function indexItems(SearchApiIndex $index, array $items);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete items from an index on this server.
 | 
			
		||||
   *
 | 
			
		||||
   * Might be either used to delete some items (given by their ids) from a
 | 
			
		||||
   * specified index, or all items from that index, or all items from all
 | 
			
		||||
   * indexes on this server.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $ids
 | 
			
		||||
   *   Either an array containing the ids of the items that should be deleted,
 | 
			
		||||
   *   or 'all' if all items should be deleted. Other formats might be
 | 
			
		||||
   *   recognized by implementing classes, but these are not standardized.
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index from which items should be deleted, or NULL if all indexes on
 | 
			
		||||
   *   this server should be cleared (then, $ids has to be 'all').
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteItems($ids = 'all', SearchApiIndex $index = NULL);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a query object for searching on an index lying on this server.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to search on.
 | 
			
		||||
   * @param $options
 | 
			
		||||
   *   Associative array of options configuring this query. See
 | 
			
		||||
   *   SearchApiQueryInterface::__construct().
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiQueryInterface
 | 
			
		||||
   *   An object for searching the given index.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If the server is currently disabled.
 | 
			
		||||
   */
 | 
			
		||||
  public function query(SearchApiIndex $index, $options = array());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes a search on the server represented by this object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $query
 | 
			
		||||
   *   The SearchApiQueryInterface object to execute.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing the search results, as required by
 | 
			
		||||
   *   SearchApiQueryInterface::execute().
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If an error prevented the search from completing.
 | 
			
		||||
   */
 | 
			
		||||
  public function search(SearchApiQueryInterface $query);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract class with generic implementation of most service methods.
 | 
			
		||||
 */
 | 
			
		||||
abstract class SearchApiAbstractService implements SearchApiServiceInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var SearchApiServer
 | 
			
		||||
   */
 | 
			
		||||
  protected $server;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Direct reference to the server's $options property.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $options = array();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructor for a service class, setting the server configuration used with
 | 
			
		||||
   * this service.
 | 
			
		||||
   *
 | 
			
		||||
   * The default implementation sets $this->server and $this->options.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiServer $server
 | 
			
		||||
   *   The server object for this service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(SearchApiServer $server) {
 | 
			
		||||
    $this->server = $server;
 | 
			
		||||
    $this->options = &$server->options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form callback. Might be called on an uninitialized object - in this case,
 | 
			
		||||
   * the form is for configuring a newly created server.
 | 
			
		||||
   *
 | 
			
		||||
   * Returns an empty form by default.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A form array for setting service-specific options.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationForm(array $form, array &$form_state) {
 | 
			
		||||
    return array();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validation callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * Does nothing by default.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormValidate(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit callback for the form returned by configurationForm().
 | 
			
		||||
   *
 | 
			
		||||
   * The default implementation just ensures that additional elements in
 | 
			
		||||
   * $options, not present in the form, don't get lost at the update.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form returned by configurationForm().
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The part of the $form_state['values'] array corresponding to this form.
 | 
			
		||||
   * @param array $form_state
 | 
			
		||||
   *   The complete form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
 | 
			
		||||
    if (!empty($this->options)) {
 | 
			
		||||
      $values += $this->options;
 | 
			
		||||
    }
 | 
			
		||||
    $this->options = $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether this service class implementation supports a given
 | 
			
		||||
   * feature. Features are optional extensions to Search API functionality and
 | 
			
		||||
   * usually defined and used by third-party modules.
 | 
			
		||||
   *
 | 
			
		||||
   * There are currently three features defined directly in the Search API
 | 
			
		||||
   * project:
 | 
			
		||||
   * - "search_api_facets", by the search_api_facetapi module.
 | 
			
		||||
   * - "search_api_facets_operator_or", also by the search_api_facetapi module.
 | 
			
		||||
   * - "search_api_mlt", by the search_api_views module.
 | 
			
		||||
   * Other contrib modules might define additional features. These should always
 | 
			
		||||
   * be properly documented in the module by which they are defined.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $feature
 | 
			
		||||
   *   The name of the optional feature.
 | 
			
		||||
   *
 | 
			
		||||
   * @return boolean
 | 
			
		||||
   *   TRUE if this service knows and supports the specified feature. FALSE
 | 
			
		||||
   *   otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function supportsFeature($feature) {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * View this server's settings. Output can be HTML or a render array, a <dl>
 | 
			
		||||
   * listing all relevant settings is preferred.
 | 
			
		||||
   *
 | 
			
		||||
   * The default implementation does a crude output as a definition list, with
 | 
			
		||||
   * option names taken from the configuration form.
 | 
			
		||||
   */
 | 
			
		||||
  public function viewSettings() {
 | 
			
		||||
    $output = '';
 | 
			
		||||
    $form = $form_state = array();
 | 
			
		||||
    $option_form = $this->configurationForm($form, $form_state);
 | 
			
		||||
    $option_names = array();
 | 
			
		||||
    foreach ($option_form as $key => $element) {
 | 
			
		||||
      if (isset($element['#title']) && isset($this->options[$key])) {
 | 
			
		||||
        $option_names[$key] = $element['#title'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($option_names as $key => $name) {
 | 
			
		||||
      $value = $this->options[$key];
 | 
			
		||||
      $output .= '<dt>' . check_plain($name) . '</dt>' . "\n";
 | 
			
		||||
      $output .= '<dd>' . nl2br(check_plain(print_r($value, TRUE))) . '</dd>' . "\n";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $output ? "<dl>\n$output</dl>" : '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called once, when the server is first created. Allows it to set up its
 | 
			
		||||
   * necessary infrastructure.
 | 
			
		||||
   *
 | 
			
		||||
   * Does nothing, by default.
 | 
			
		||||
   */
 | 
			
		||||
  public function postCreate() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies this server that its fields are about to be updated. The server's
 | 
			
		||||
   * $original property can be used to inspect the old property values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, if the update requires reindexing of all content on the server.
 | 
			
		||||
   */
 | 
			
		||||
  public function postUpdate() {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies this server that it is about to be deleted from the database and
 | 
			
		||||
   * should therefore clean up, if appropriate.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that you shouldn't call the server's save() method, or any
 | 
			
		||||
   * methods that might do that, from inside of this method as the server isn't
 | 
			
		||||
   * present in the database anymore at this point.
 | 
			
		||||
   *
 | 
			
		||||
   * By default, deletes all indexes from this server.
 | 
			
		||||
   */
 | 
			
		||||
  public function preDelete() {
 | 
			
		||||
    $indexes = search_api_index_load_multiple(FALSE, array('server' => $this->server->machine_name));
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $this->removeIndex($index);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a new index to this server.
 | 
			
		||||
   *
 | 
			
		||||
   * Does nothing, by default.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to add.
 | 
			
		||||
   */
 | 
			
		||||
  public function addIndex(SearchApiIndex $index) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notify the server that the indexed field settings for the index have
 | 
			
		||||
   * changed.
 | 
			
		||||
   * If any user action is necessary as a result of this, the method should
 | 
			
		||||
   * use drupal_set_message() to notify the user.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The updated index.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   *   TRUE, if this change affected the server in any way that forces it to
 | 
			
		||||
   *   re-index the content. FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public function fieldsUpdated(SearchApiIndex $index) {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove an index from this server.
 | 
			
		||||
   *
 | 
			
		||||
   * This might mean that the index has been deleted, or reassigned to a
 | 
			
		||||
   * different server. If you need to distinguish between these cases, inspect
 | 
			
		||||
   * $index->server.
 | 
			
		||||
   *
 | 
			
		||||
   * By default, removes all items from that index.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $index
 | 
			
		||||
   *   Either an object representing the index to remove, or its machine name
 | 
			
		||||
   *   (if the index was completely deleted).
 | 
			
		||||
   */
 | 
			
		||||
  public function removeIndex($index) {
 | 
			
		||||
    $this->deleteItems('all', $index);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a query object for searching on an index lying on this server.
 | 
			
		||||
   *
 | 
			
		||||
   * @param SearchApiIndex $index
 | 
			
		||||
   *   The index to search on.
 | 
			
		||||
   * @param $options
 | 
			
		||||
   *   Associative array of options configuring this query. See
 | 
			
		||||
   *   SearchApiQueryInterface::__construct().
 | 
			
		||||
   *
 | 
			
		||||
   * @return SearchApiQueryInterface
 | 
			
		||||
   *   An object for searching the given index.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws SearchApiException
 | 
			
		||||
   *   If the server is currently disabled.
 | 
			
		||||
   */
 | 
			
		||||
  public function query(SearchApiIndex $index, $options = array()) {
 | 
			
		||||
    return new SearchApiQuery($index, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								search_api.admin.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								search_api.admin.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
 | 
			
		||||
td.search-api-status {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.search-api-edit-menu {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  color: black;
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
  border: 1px solid black;
 | 
			
		||||
  -moz-border-radius: 4px;
 | 
			
		||||
  -webkit-border-radius: 4px;
 | 
			
		||||
  -khtml-border-radius: 4px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.search-api-edit-menu ul {
 | 
			
		||||
  margin: 0 0.5em;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.search-api-edit-menu ul li {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.search-api-edit-menu.collapsed {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-api-alter-add-aggregation-fields,
 | 
			
		||||
.search-api-checkboxes-list {
 | 
			
		||||
  max-height: 12em;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Workaround for http://drupal.org/node/1015798 */
 | 
			
		||||
.vertical-tabs fieldset div.fieldset-wrapper fieldset legend {
 | 
			
		||||
  display: block;
 | 
			
		||||
  margin-bottom: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1970
									
								
								search_api.admin.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1970
									
								
								search_api.admin.inc
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										61
									
								
								search_api.admin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								search_api.admin.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
 | 
			
		||||
// Copied from filter.admin.js
 | 
			
		||||
(function ($) {
 | 
			
		||||
 | 
			
		||||
Drupal.behaviors.searchApiStatus = {
 | 
			
		||||
  attach: function (context, settings) {
 | 
			
		||||
    $('.search-api-status-wrapper input.form-checkbox', context).once('search-api-status', function () {
 | 
			
		||||
      var $checkbox = $(this);
 | 
			
		||||
      // Retrieve the tabledrag row belonging to this processor.
 | 
			
		||||
      var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
 | 
			
		||||
      // Retrieve the vertical tab belonging to this processor.
 | 
			
		||||
      var $tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
 | 
			
		||||
 | 
			
		||||
      // Bind click handler to this checkbox to conditionally show and hide the
 | 
			
		||||
      // filter's tableDrag row and vertical tab pane.
 | 
			
		||||
      $checkbox.bind('click.searchApiUpdate', function () {
 | 
			
		||||
        if ($checkbox.is(':checked')) {
 | 
			
		||||
          $row.show();
 | 
			
		||||
          if ($tab) {
 | 
			
		||||
            $tab.tabShow().updateSummary();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $row.hide();
 | 
			
		||||
          if ($tab) {
 | 
			
		||||
            $tab.tabHide().updateSummary();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // Restripe table after toggling visibility of table row.
 | 
			
		||||
        Drupal.tableDrag['search-api-' + $checkbox.attr('id').replace(/^edit-([^-]+)-.*$/, '$1') + '-order-table'].restripeTable();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Attach summary for configurable items (only for screen-readers).
 | 
			
		||||
      if ($tab) {
 | 
			
		||||
        $tab.fieldset.drupalSetSummary(function (tabContext) {
 | 
			
		||||
          return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Trigger our bound click handler to update elements to initial state.
 | 
			
		||||
      $checkbox.triggerHandler('click.searchApiUpdate');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Drupal.behaviors.searchApiEditMenu = {
 | 
			
		||||
  attach: function (context, settings) {
 | 
			
		||||
    $('.search-api-edit-menu-toggle', context).click(function (e) {
 | 
			
		||||
      $menu = $(this).parent().find('.search-api-edit-menu');
 | 
			
		||||
      if ($menu.is('.collapsed')) {
 | 
			
		||||
    	$menu.removeClass('collapsed');
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
    	$menu.addClass('collapsed');
 | 
			
		||||
      }
 | 
			
		||||
      return false;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
})(jQuery);
 | 
			
		||||
							
								
								
									
										535
									
								
								search_api.api.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								search_api.api.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,535 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Hooks provided by the Search API module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @addtogroup hooks
 | 
			
		||||
 * @{
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines one or more search service classes a module offers.
 | 
			
		||||
 *
 | 
			
		||||
 * Note: The ids should be valid PHP identifiers.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_service_info_alter()
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An associative array of search service classes, keyed by a unique
 | 
			
		||||
 *   identifier and containing associative arrays with the following keys:
 | 
			
		||||
 *   - name: The service class' translated name.
 | 
			
		||||
 *   - description: A translated string to be shown to administrators when
 | 
			
		||||
 *     selecting a service class. Should contain all peculiarities of the
 | 
			
		||||
 *     service class, like field type support, supported features (like facets),
 | 
			
		||||
 *     the "direct" parse mode and other specific things to keep in mind.
 | 
			
		||||
 *   - class: The service class, which has to implement the
 | 
			
		||||
 *     SearchApiServiceInterface interface.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_service_info() {
 | 
			
		||||
  $services['example_some'] = array(
 | 
			
		||||
    'name' => t('Some Service'),
 | 
			
		||||
    'description' => t('Service for some search engine.'),
 | 
			
		||||
    'class' => 'SomeServiceClass',
 | 
			
		||||
    // Unknown keys can later be read by the object for additional information.
 | 
			
		||||
    'init args' => array('foo' => 'Foo', 'bar' => 42),
 | 
			
		||||
  );
 | 
			
		||||
  $services['example_other'] = array(
 | 
			
		||||
    'name' => t('Other Service'),
 | 
			
		||||
    'description' => t('Service for another search engine.'),
 | 
			
		||||
    'class' => 'OtherServiceClass',
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $services;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Alter the Search API service info.
 | 
			
		||||
 *
 | 
			
		||||
 * Modules may implement this hook to alter the information that defines Search
 | 
			
		||||
 * API service. All properties that are available in
 | 
			
		||||
 * hook_search_api_service_info() can be altered here.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_service_info()
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $service_info
 | 
			
		||||
 *   The Search API service info array, keyed by service id.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_service_info_alter(array &$service_info) {
 | 
			
		||||
  foreach ($service_info as $id => $info) {
 | 
			
		||||
    $service_info[$id]['class'] = 'MyProxyServiceClass';
 | 
			
		||||
    $service_info[$id]['example_original_class'] = $info['class'];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Define new types of items that can be searched.
 | 
			
		||||
 *
 | 
			
		||||
 * This hook allows modules to define their own item types, for which indexes
 | 
			
		||||
 * can then be created. (Note that the Search API natively provides support for
 | 
			
		||||
 * all entity types that specify property information, so they should not be
 | 
			
		||||
 * added here. You should therefore also not use an existing entity type as the
 | 
			
		||||
 * identifier of a new item type.)
 | 
			
		||||
 *
 | 
			
		||||
 * The main part of defining a new item type is implementing its data source
 | 
			
		||||
 * controller class, which is responsible for loading items, providing metadata
 | 
			
		||||
 * and tracking existing items. The module defining a certain item type is also
 | 
			
		||||
 * responsible for observing creations, updates and deletions of items of that
 | 
			
		||||
 * type and notifying the Search API of them by calling
 | 
			
		||||
 * search_api_track_item_insert(), search_api_track_item_change() and
 | 
			
		||||
 * search_api_track_item_delete(), as appropriate.
 | 
			
		||||
 * The only other restriction for item types is that they have to have a single
 | 
			
		||||
 * item ID field, with a scalar value. This is, e.g., used to track indexed
 | 
			
		||||
 * items.
 | 
			
		||||
 *
 | 
			
		||||
 * Note, however, that you can also define item types where some of these
 | 
			
		||||
 * conditions are not met, as long as you are aware that some functionality of
 | 
			
		||||
 * the Search API and related modules might then not be available for that type.
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An associative array keyed by item type identifier, and containing type
 | 
			
		||||
 *   information arrays with at least the following keys:
 | 
			
		||||
 *   - name: A human-readable name for the type.
 | 
			
		||||
 *   - datasource controller: A class implementing the
 | 
			
		||||
 *     SearchApiDataSourceControllerInterface interface which will be used as
 | 
			
		||||
 *     the data source controller for this type.
 | 
			
		||||
 *   Other, datasource-specific settings might also be placed here. These should
 | 
			
		||||
 *   be specified with the data source controller in question.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_item_type_info_alter()
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_item_type_info() {
 | 
			
		||||
  // Copied from search_api_search_api_item_type_info().
 | 
			
		||||
  $types = array();
 | 
			
		||||
 | 
			
		||||
  foreach (entity_get_property_info() as $type => $property_info) {
 | 
			
		||||
    if ($info = entity_get_info($type)) {
 | 
			
		||||
      $types[$type] = array(
 | 
			
		||||
        'name' => $info['label'],
 | 
			
		||||
        'datasource controller' => 'SearchApiEntityDataSourceController',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return $types;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Alter the item type info.
 | 
			
		||||
 *
 | 
			
		||||
 * Modules may implement this hook to alter the information that defines an
 | 
			
		||||
 * item type. All properties that are available in
 | 
			
		||||
 * hook_search_api_item_type_info() can be altered here.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $infos
 | 
			
		||||
 *   The item type info array, keyed by type identifier.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_item_type_info()
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_item_type_info_alter(array &$infos) {
 | 
			
		||||
  // Adds a boolean value is_entity to all type options telling whether the item
 | 
			
		||||
  // type represents an entity type.
 | 
			
		||||
  foreach ($infos as $type => $info) {
 | 
			
		||||
    $info['is_entity'] = (bool) entity_get_info($type);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Define new data types for indexed properties.
 | 
			
		||||
 *
 | 
			
		||||
 * New data types will appear as new option for the „Type“ field on indexes'
 | 
			
		||||
 * „Fields“ tabs. Whether choosing a custom data type will have any effect
 | 
			
		||||
 * depends on the server on which the data is indexed.
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An array containing custom data type definitions, keyed by their type
 | 
			
		||||
 *   identifier and containing the following keys:
 | 
			
		||||
 *   - name: The human-readable name of the type.
 | 
			
		||||
 *   - fallback: (optional) One of the default data types (the keys from
 | 
			
		||||
 *     search_api_default_field_types()) which should be used as a fallback if
 | 
			
		||||
 *     the server doesn't support this data type. Defaults to "string".
 | 
			
		||||
 *   - conversion callback: (optional) If specified, a callback function for
 | 
			
		||||
 *     converting raw values to the given type, if possible. For the contract
 | 
			
		||||
 *     of such a callback, see example_data_type_conversion().
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_data_type_info_alter()
 | 
			
		||||
 * @see search_api_get_data_type_info()
 | 
			
		||||
 * @see example_data_type_conversion()
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_data_type_info() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'example_type' => array(
 | 
			
		||||
      'name' => t('Example type'),
 | 
			
		||||
      // Could be omitted, as "string" is the default.
 | 
			
		||||
      'fallback' => 'string',
 | 
			
		||||
      'conversion callback' => 'example_data_type_conversion',
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Alter the data type info.
 | 
			
		||||
 *
 | 
			
		||||
 * Modules may implement this hook to alter the information that defines a data
 | 
			
		||||
 * type, or to add/remove some entirely. All properties that are available in
 | 
			
		||||
 * hook_search_api_data_type_info() can be altered here.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $infos
 | 
			
		||||
 *   The data type info array, keyed by type identifier.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_data_type_info()
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_data_type_info_alter(array &$infos) {
 | 
			
		||||
  $infos['example_type']['name'] .= ' 2';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Registers one or more callbacks that can be called at index time to add
 | 
			
		||||
 * additional data to the indexed items (e.g. comments or attachments to nodes),
 | 
			
		||||
 * alter the data in other forms or remove items from the array.
 | 
			
		||||
 *
 | 
			
		||||
 * Data-alter callbacks (which are called "Data alterations" in the UI) are
 | 
			
		||||
 * classes implementing the SearchApiAlterCallbackInterface interface.
 | 
			
		||||
 *
 | 
			
		||||
 * @see SearchApiAlterCallbackInterface
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An associative array keyed by the callback IDs and containing arrays with
 | 
			
		||||
 *   the following keys:
 | 
			
		||||
 *   - name: The name to display for this callback.
 | 
			
		||||
 *   - description: A short description of what the callback does.
 | 
			
		||||
 *   - class: The callback class.
 | 
			
		||||
 *   - weight: (optional) Defines the order in which callbacks are displayed
 | 
			
		||||
 *     (and, therefore, invoked) by default. Defaults to 0.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_alter_callback_info() {
 | 
			
		||||
  $callbacks['example_random_alter'] = array(
 | 
			
		||||
    'name' => t('Random alteration'),
 | 
			
		||||
    'description' => t('Alters all passed item data completely randomly.'),
 | 
			
		||||
    'class' => 'ExampleRandomAlter',
 | 
			
		||||
    'weight' => 100,
 | 
			
		||||
  );
 | 
			
		||||
  $callbacks['example_add_comments'] = array(
 | 
			
		||||
    'name' => t('Add comments'),
 | 
			
		||||
    'description' => t('For nodes and similar entities, adds comments.'),
 | 
			
		||||
    'class' => 'ExampleAddComments',
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $callbacks;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Registers one or more processors. These are classes implementing the
 | 
			
		||||
 * SearchApiProcessorInterface interface which can be used at index and search
 | 
			
		||||
 * time to pre-process item data or the search query, and at search time to
 | 
			
		||||
 * post-process the returned search results.
 | 
			
		||||
 *
 | 
			
		||||
 * @see SearchApiProcessorInterface
 | 
			
		||||
 *
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An associative array keyed by the processor id and containing arrays
 | 
			
		||||
 *   with the following keys:
 | 
			
		||||
 *   - name: The name to display for this processor.
 | 
			
		||||
 *   - description: A short description of what the processor does at each
 | 
			
		||||
 *     phase.
 | 
			
		||||
 *   - class: The processor class, which has to implement the
 | 
			
		||||
 *     SearchApiProcessorInterface interface.
 | 
			
		||||
 *   - weight: (optional) Defines the order in which processors are displayed
 | 
			
		||||
 *     (and, therefore, invoked) by default. Defaults to 0.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_processor_info() {
 | 
			
		||||
  $callbacks['example_processor'] = array(
 | 
			
		||||
    'name' => t('Example processor'),
 | 
			
		||||
    'description' => t('Pre- and post-processes data in really cool ways.'),
 | 
			
		||||
    'class' => 'ExampleSearchApiProcessor',
 | 
			
		||||
    'weight' => -1,
 | 
			
		||||
  );
 | 
			
		||||
  $callbacks['example_processor_minimal'] = array(
 | 
			
		||||
    'name' => t('Example processor 2'),
 | 
			
		||||
    'description' => t('Processor with minimal description.'),
 | 
			
		||||
    'class' => 'ExampleSearchApiProcessor2',
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $callbacks;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Allows you to log or alter the items that are indexed.
 | 
			
		||||
 *
 | 
			
		||||
 * Please be aware that generally preventing the indexing of certain items is
 | 
			
		||||
 * deprecated. This is better done with data alterations, which can easily be
 | 
			
		||||
 * configured and only added to indexes where this behaviour is wanted.
 | 
			
		||||
 * If your module will use this hook to reject certain items from indexing,
 | 
			
		||||
 * please document this clearly to avoid confusion.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $items
 | 
			
		||||
 *   The entities that will be indexed (before calling any data alterations).
 | 
			
		||||
 * @param SearchApiIndex $index
 | 
			
		||||
 *   The search index on which items will be indexed.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_items_alter(array &$items, SearchApiIndex $index) {
 | 
			
		||||
  foreach ($items as $id => $item) {
 | 
			
		||||
    if ($id % 5 == 0) {
 | 
			
		||||
      unset($items[$id]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  example_store_indexed_entity_ids($index->item_type, array_keys($items));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Lets modules alter a search query before executing it.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiQueryInterface $query
 | 
			
		||||
 *   The SearchApiQueryInterface object representing the search query.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_query_alter(SearchApiQueryInterface $query) {
 | 
			
		||||
  $info = entity_get_info($index->item_type);
 | 
			
		||||
  $query->condition($info['entity keys']['id'], 0, '!=');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Act on search servers when they are loaded.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $servers
 | 
			
		||||
 *   An array of loaded SearchApiServer objects.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_server_load(array $servers) {
 | 
			
		||||
  foreach ($servers as $server) {
 | 
			
		||||
    db_insert('example_search_server_access')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'server' => $server->machine_name,
 | 
			
		||||
        'access_time' => REQUEST_TIME,
 | 
			
		||||
      ))
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A new search server was created.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiServer $server
 | 
			
		||||
 *   The new server.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_server_insert(SearchApiServer $server) {
 | 
			
		||||
  db_insert('example_search_server')
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'server' => $server->machine_name,
 | 
			
		||||
      'insert_time' => REQUEST_TIME,
 | 
			
		||||
    ))
 | 
			
		||||
    ->execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A search server was edited, enabled or disabled.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiServer $server
 | 
			
		||||
 *   The edited server.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_server_update(SearchApiServer $server) {
 | 
			
		||||
  if ($server->name != $server->original->name) {
 | 
			
		||||
    db_insert('example_search_server_name_update')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'server' => $server->machine_name,
 | 
			
		||||
        'update_time' => REQUEST_TIME,
 | 
			
		||||
      ))
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A search server was deleted.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiServer $server
 | 
			
		||||
 *   The deleted server.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_server_delete(SearchApiServer $server) {
 | 
			
		||||
  db_insert('example_search_server_update')
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'server' => $server->machine_name,
 | 
			
		||||
      'update_time' => REQUEST_TIME,
 | 
			
		||||
    ))
 | 
			
		||||
    ->execute();
 | 
			
		||||
  db_delete('example_search_server')
 | 
			
		||||
    ->condition('server', $server->machine_name)
 | 
			
		||||
    ->execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Define default search servers.
 | 
			
		||||
*
 | 
			
		||||
* @return array
 | 
			
		||||
*   An array of default search servers, keyed by machine names.
 | 
			
		||||
*
 | 
			
		||||
* @see hook_default_search_api_server_alter()
 | 
			
		||||
*/
 | 
			
		||||
function hook_default_search_api_server() {
 | 
			
		||||
  $defaults['main'] = entity_create('search_api_server', array(
 | 
			
		||||
    'name' => 'Main server',
 | 
			
		||||
    'machine_name' => 'main',// Must be same as the used array key.
 | 
			
		||||
    // Other properties ...
 | 
			
		||||
  ));
 | 
			
		||||
  return $defaults;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Alter default search servers.
 | 
			
		||||
*
 | 
			
		||||
* @param array $defaults
 | 
			
		||||
*   An array of default search servers, keyed by machine names.
 | 
			
		||||
*
 | 
			
		||||
* @see hook_default_search_api_server()
 | 
			
		||||
*/
 | 
			
		||||
function hook_default_search_api_server_alter(array &$defaults) {
 | 
			
		||||
  $defaults['main']->name = 'Customized main server';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Act on search indexes when they are loaded.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $indexes
 | 
			
		||||
 *   An array of loaded SearchApiIndex objects.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_load(array $indexes) {
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    db_insert('example_search_index_access')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'index' => $index->machine_name,
 | 
			
		||||
        'access_time' => REQUEST_TIME,
 | 
			
		||||
      ))
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A new search index was created.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiIndex $index
 | 
			
		||||
 *   The new index.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_insert(SearchApiIndex $index) {
 | 
			
		||||
  db_insert('example_search_index')
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'index' => $index->machine_name,
 | 
			
		||||
      'insert_time' => REQUEST_TIME,
 | 
			
		||||
    ))
 | 
			
		||||
    ->execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A search index was edited in any way.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiIndex $index
 | 
			
		||||
 *   The edited index.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_update(SearchApiIndex $index) {
 | 
			
		||||
  if ($index->name != $index->original->name) {
 | 
			
		||||
    db_insert('example_search_index_name_update')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'index' => $index->machine_name,
 | 
			
		||||
        'update_time' => REQUEST_TIME,
 | 
			
		||||
      ))
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A search index was scheduled for reindexing
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiIndex $index
 | 
			
		||||
 *   The edited index.
 | 
			
		||||
 * @param $clear
 | 
			
		||||
 *   Boolean indicating whether the index was also cleared.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_reindex(SearchApiIndex $index, $clear = FALSE) {
 | 
			
		||||
  db_insert('example_search_index_reindexed')
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'index' => $index->id,
 | 
			
		||||
      'update_time' => REQUEST_TIME,
 | 
			
		||||
    ))
 | 
			
		||||
    ->execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A search index was deleted.
 | 
			
		||||
 *
 | 
			
		||||
 * @param SearchApiIndex $index
 | 
			
		||||
 *   The deleted index.
 | 
			
		||||
 */
 | 
			
		||||
function hook_search_api_index_delete(SearchApiIndex $index) {
 | 
			
		||||
  db_insert('example_search_index_update')
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'index' => $index->machine_name,
 | 
			
		||||
      'update_time' => REQUEST_TIME,
 | 
			
		||||
    ))
 | 
			
		||||
    ->execute();
 | 
			
		||||
  db_delete('example_search_index')
 | 
			
		||||
    ->condition('index', $index->machine_name)
 | 
			
		||||
    ->execute();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Define default search indexes.
 | 
			
		||||
*
 | 
			
		||||
* @return array
 | 
			
		||||
*   An array of default search indexes, keyed by machine names.
 | 
			
		||||
*
 | 
			
		||||
* @see hook_default_search_api_index_alter()
 | 
			
		||||
*/
 | 
			
		||||
function hook_default_search_api_index() {
 | 
			
		||||
  $defaults['main'] = entity_create('search_api_index', array(
 | 
			
		||||
    'name' => 'Main index',
 | 
			
		||||
    'machine_name' => 'main',// Must be same as the used array key.
 | 
			
		||||
    // Other properties ...
 | 
			
		||||
  ));
 | 
			
		||||
  return $defaults;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Alter default search indexes.
 | 
			
		||||
*
 | 
			
		||||
* @param array $defaults
 | 
			
		||||
*   An array of default search indexes, keyed by machine names.
 | 
			
		||||
*
 | 
			
		||||
* @see hook_default_search_api_index()
 | 
			
		||||
*/
 | 
			
		||||
function hook_default_search_api_index_alter(array &$defaults) {
 | 
			
		||||
  $defaults['main']->name = 'Customized main index';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @} End of "addtogroup hooks".
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert a raw value from an entity to a custom data type.
 | 
			
		||||
 *
 | 
			
		||||
 * This function will be called for fields of the specific data type to convert
 | 
			
		||||
 * all individual values of the field to the correct format.
 | 
			
		||||
 *
 | 
			
		||||
 * @param $value
 | 
			
		||||
 *   The raw, single value, as extracted from an entity wrapper.
 | 
			
		||||
 * @param $original_type
 | 
			
		||||
 *   The original Entity API type of the value.
 | 
			
		||||
 * @param $type
 | 
			
		||||
 *   The custom data type to which the value should be converted. Can be ignored
 | 
			
		||||
 *   if the callback is only used for a single data type.
 | 
			
		||||
 *
 | 
			
		||||
 * @return
 | 
			
		||||
 *   The converted value, if a conversion could be executed. NULL otherwise.
 | 
			
		||||
 *
 | 
			
		||||
 * @see hook_search_api_data_type_info()
 | 
			
		||||
 */
 | 
			
		||||
function example_data_type_conversion($value, $original_type, $type) {
 | 
			
		||||
  if ($type === 'example_type') {
 | 
			
		||||
    // The example_type type apparently requires a rather complex data format.
 | 
			
		||||
    return array(
 | 
			
		||||
      'value' => $value,
 | 
			
		||||
      'original' => $original_type,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  // Someone used this callback for another, unknown type. Return NULL.
 | 
			
		||||
  // (Normally, you can just assume that the/a correct type is given.)
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										311
									
								
								search_api.drush.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								search_api.drush.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Drush commands for SearchAPI.
 | 
			
		||||
 *
 | 
			
		||||
 * Original file by agentrickard for Palantir.net
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_drush_command().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_drush_command() {
 | 
			
		||||
  $items = array();
 | 
			
		||||
 | 
			
		||||
  $items['search-api-list'] = array(
 | 
			
		||||
    'description' => 'List all search indexes.',
 | 
			
		||||
    'examples' => array(
 | 
			
		||||
      'drush searchapi-list' => dt('List all search indexes.'),
 | 
			
		||||
      'drush sapi-l' => dt('Alias to list all search indexes.'),
 | 
			
		||||
    ),
 | 
			
		||||
    'aliases' => array('sapi-l'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $items['search-api-status'] = array(
 | 
			
		||||
    'description' => 'Show the status of one or all search indexes.',
 | 
			
		||||
    'examples' => array(
 | 
			
		||||
      'drush searchapi-status' => dt('Show the status of all search indexes.'),
 | 
			
		||||
      'drush sapi-s' => dt('Alias to show the status of all search indexes.'),
 | 
			
		||||
      'drush sapi-s 1' => dt('Show the status of the search index with the ID !id.', array('!id' => 1)),
 | 
			
		||||
      'drush sapi-s default_node_index' => dt('Show the status of the search index with the machine name !name.', array('!name' => 'default_node_index')),
 | 
			
		||||
    ),
 | 
			
		||||
    'arguments' => array(
 | 
			
		||||
      'index_id' => dt('The numeric ID or machine name of an index.'),
 | 
			
		||||
    ),
 | 
			
		||||
    'aliases' => array('sapi-s'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $items['search-api-index'] = array(
 | 
			
		||||
    'description' => 'Index items for one or all enabled search_api indexes.',
 | 
			
		||||
    'examples' => array(
 | 
			
		||||
      'drush searchapi-index' => dt('Index items for all enabled indexes.'),
 | 
			
		||||
      'drush sapi-i' => dt('Alias to index items for all enabled indexes.'),
 | 
			
		||||
      'drush sapi-i 1' => dt('Index items for the index with the ID !id.', array('!id' => 1)),
 | 
			
		||||
      'drush sapi-i default_node_index' => dt('Index items for the index with the machine name !name.', array('!name' => 'default_node_index')),
 | 
			
		||||
      'drush sapi-i 1 100' => dt("Index a maximum number of !limit items (index's cron batch size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!id' => 1)),
 | 
			
		||||
      'drush sapi-i 1 100 10' => dt("Index a maximum number of !limit items (!batch_size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!batch_size' => 10, '!id' => 1)),
 | 
			
		||||
    ),
 | 
			
		||||
    'arguments' => array(
 | 
			
		||||
      'index_id' => dt('The numeric ID or machine name of an index.'),
 | 
			
		||||
      'limit' => dt("The number of items to index (index's cron batch size items per run). Set to 0 to index all items. Defaults to 0 (index all)."),
 | 
			
		||||
      'batch_size' => dt("The number of items to index per batch run. Set to 0 to index all items at once. Defaults to the index's cron batch size."),
 | 
			
		||||
    ),
 | 
			
		||||
    'aliases' => array('sapi-i'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $items['search-api-reindex'] = array(
 | 
			
		||||
    'description' => 'Force reindexing of one or all search indexes, without clearing existing index data.',
 | 
			
		||||
    'examples' => array(
 | 
			
		||||
      'drush searchapi-reindex' => dt('Schedule all search indexes for reindexing.'),
 | 
			
		||||
      'drush sapi-r' => dt('Alias to schedule all search indexes for reindexing .'),
 | 
			
		||||
      'drush sapi-r 1' => dt('Schedule the search index with the ID !id for re-indexing.', array('!id' => 1)),
 | 
			
		||||
      'drush sapi-r default_node_index' => dt('Schedule the search index with the machine name !name for re-indexing.', array('!name' => 'default_node_index')),
 | 
			
		||||
    ),
 | 
			
		||||
    'arguments' => array(
 | 
			
		||||
      'index_id' => dt('The numeric ID or machine name of an index.'),
 | 
			
		||||
    ),
 | 
			
		||||
    'aliases' => array('sapi-r'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $items['search-api-clear'] = array(
 | 
			
		||||
    'description' => 'Clear one or all search indexes and mark them for re-indexing.',
 | 
			
		||||
    'examples' => array(
 | 
			
		||||
      'drush searchapi-clear' => dt('Clear all search indexes.'),
 | 
			
		||||
      'drush sapi-c' => dt('Alias to clear all search indexes.'),
 | 
			
		||||
      'drush sapi-r 1' => dt('Clear the search index with the ID !id.', array('!id' => 1)),
 | 
			
		||||
      'drush sapi-r default_node_index' => dt('Clear the search index with the machine name !name.', array('!name' => 'default_node_index')),
 | 
			
		||||
    ),
 | 
			
		||||
    'arguments' => array(
 | 
			
		||||
      'index_id' => dt('The numeric ID or machine name of an index.'),
 | 
			
		||||
    ),
 | 
			
		||||
    'aliases' => array('sapi-c'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * List all search indexes.
 | 
			
		||||
 */
 | 
			
		||||
function drush_search_api_list() {
 | 
			
		||||
  if (search_api_drush_static(__FUNCTION__)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // See search_api_list_indexes()
 | 
			
		||||
  $indexes = search_api_index_load_multiple(FALSE);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    drush_print(dt('There are no indexes present.'));
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  $rows[] = array(
 | 
			
		||||
    dt('Id'),
 | 
			
		||||
    dt('Name'),
 | 
			
		||||
    dt('Index'),
 | 
			
		||||
    dt('Server'),
 | 
			
		||||
    dt('Type'),
 | 
			
		||||
    dt('Status'),
 | 
			
		||||
    dt('Limit'),
 | 
			
		||||
  );
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    $type = search_api_get_item_type_info($index->item_type);
 | 
			
		||||
    $type = isset($type['name']) ? $type['name'] : $index->item_type;
 | 
			
		||||
    $server = $index->server();
 | 
			
		||||
    $server = $server ? $server->name : '(' . t('none') . ')';
 | 
			
		||||
    $row = array(
 | 
			
		||||
      $index->id,
 | 
			
		||||
      $index->name,
 | 
			
		||||
      $index->machine_name,
 | 
			
		||||
      $server,
 | 
			
		||||
      $type,
 | 
			
		||||
      $index->enabled ? t('enabled') : t('disabled'),
 | 
			
		||||
      $index->options['cron_limit'],
 | 
			
		||||
    );
 | 
			
		||||
    $rows[] = $row;
 | 
			
		||||
  }
 | 
			
		||||
  drush_print_table($rows);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display index status.
 | 
			
		||||
 */
 | 
			
		||||
function drush_search_api_status($index_id = NULL) {
 | 
			
		||||
  if (search_api_drush_static(__FUNCTION__)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  $indexes = search_api_drush_get_index($index_id);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // See search_api_index_status()
 | 
			
		||||
  $rows = array(array(
 | 
			
		||||
    dt('Id'),
 | 
			
		||||
    dt('Index'),
 | 
			
		||||
    dt('% Complete'),
 | 
			
		||||
    dt('Indexed'),
 | 
			
		||||
    dt('Total'),
 | 
			
		||||
  ));
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    $status = search_api_index_status($index);
 | 
			
		||||
    $complete = ($status['total'] > 0) ? 100 * round($status['indexed'] / $status['total'], 3) . '%' : '-';
 | 
			
		||||
    $row = array(
 | 
			
		||||
      $index->id,
 | 
			
		||||
      $index->name,
 | 
			
		||||
      $complete,
 | 
			
		||||
      $status['indexed'],
 | 
			
		||||
      $status['total'],
 | 
			
		||||
    );
 | 
			
		||||
    $rows[] = $row;
 | 
			
		||||
  }
 | 
			
		||||
  drush_print_table($rows);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Index items.
 | 
			
		||||
 *
 | 
			
		||||
 * @param string|integer $index_id
 | 
			
		||||
 *   The index name or id for which items should be indexed.
 | 
			
		||||
 * @param integer $limit
 | 
			
		||||
 *   Maximum number of items to index.
 | 
			
		||||
 * @param integer $batch_size
 | 
			
		||||
 *   Number of items to index per batch.
 | 
			
		||||
 */
 | 
			
		||||
function drush_search_api_index($index_id = NULL, $limit = NULL, $batch_size = NULL) {
 | 
			
		||||
  if (search_api_drush_static(__FUNCTION__)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  $indexes = search_api_drush_get_index($index_id);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    // Get the number of remaing items to index.
 | 
			
		||||
    $datasource = $index->datasource();
 | 
			
		||||
    $index_status = $datasource->getIndexStatus($index);
 | 
			
		||||
    $remaining = $index_status['total'] - $index_status['indexed'];
 | 
			
		||||
 | 
			
		||||
    // Get the number of items to index per batch run.
 | 
			
		||||
    if (!isset($batch_size)) {
 | 
			
		||||
      $batch_size = empty($index->options['cron_limit']) ? SEARCH_API_DEFAULT_CRON_LIMIT : $index->options['cron_limit'];
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($batch_size <= 0) {
 | 
			
		||||
      $batch_size = $remaining;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get the number items to index.
 | 
			
		||||
    if (!isset($limit) || !is_int($limit += 0) || $limit <= 0) {
 | 
			
		||||
      $limit = $remaining;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    drush_log(dt("Indexing a maximum number of !limit items (!batch_size items per batch run) for the index !index.", array('!index' => $index->name, '!limit' => $limit, '!batch_size' => $batch_size)), 'ok');
 | 
			
		||||
 | 
			
		||||
    // Create the batch.
 | 
			
		||||
    if (!_search_api_batch_indexing_create($index, $batch_size, $limit, $remaining, TRUE)) {
 | 
			
		||||
      drush_log(dt("Couldn't create a batch, please check the batch size and limit parameters."), 'error');
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Launch the batch process.
 | 
			
		||||
      drush_backend_batch_process();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy of formal_plural that works with drush as 't' may not be available.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_drush_format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
 | 
			
		||||
  $args['@count'] = $count;
 | 
			
		||||
  if ($count == 1) {
 | 
			
		||||
    return dt($singular, $args, $options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get the plural index through the gettext formula.
 | 
			
		||||
  $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
 | 
			
		||||
  // If the index cannot be computed, use the plural as a fallback (which
 | 
			
		||||
  // allows for most flexiblity with the replaceable @count value).
 | 
			
		||||
  if ($index < 0) {
 | 
			
		||||
    return dt($plural, $args, $options);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    switch ($index) {
 | 
			
		||||
      case "0":
 | 
			
		||||
        return dt($singular, $args, $options);
 | 
			
		||||
      case "1":
 | 
			
		||||
        return dt($plural, $args, $options);
 | 
			
		||||
      default:
 | 
			
		||||
        unset($args['@count']);
 | 
			
		||||
        $args['@count[' . $index . ']'] = $count;
 | 
			
		||||
        return dt(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Mark for re-indexing.
 | 
			
		||||
 */
 | 
			
		||||
function drush_search_api_reindex($index_id = NULL) {
 | 
			
		||||
  if (search_api_drush_static(__FUNCTION__)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  $indexes = search_api_drush_get_index($index_id);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // See search_api_index_reindex()
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    $index->reindex();
 | 
			
		||||
    drush_log(dt('!index was successfully marked for re-indexing.', array('!index' => $index->machine_name)), 'ok');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clear an index.
 | 
			
		||||
 */
 | 
			
		||||
function drush_search_api_clear($index_id = NULL) {
 | 
			
		||||
  if (search_api_drush_static(__FUNCTION__)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  $indexes = search_api_drush_get_index($index_id);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // See search_api_index_reindex()
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    $index->clear();
 | 
			
		||||
    drush_log(dt('!index was successfully cleared.', array('!index' => $index->machine_name)), 'ok');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper function to return an index or all indexes as an array.
 | 
			
		||||
 *
 | 
			
		||||
 * @param $index_id
 | 
			
		||||
 *   (optional) The provided index id.
 | 
			
		||||
 *
 | 
			
		||||
 * @return
 | 
			
		||||
 *   An array of indexes.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_drush_get_index($index_id = NULL) {
 | 
			
		||||
  $ids = isset($index_id) ? array($index_id) : FALSE;
 | 
			
		||||
  $indexes = search_api_index_load_multiple($ids);
 | 
			
		||||
  if (empty($indexes)) {
 | 
			
		||||
    drush_set_error(dt('Invalid index_id or no indexes present. Listing all indexes:'));
 | 
			
		||||
    drush_print();
 | 
			
		||||
    drush_search_api_list();
 | 
			
		||||
  }
 | 
			
		||||
  return $indexes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Static lookup to prevent Drush 4 from running twice.
 | 
			
		||||
 *
 | 
			
		||||
 * @see http://drupal.org/node/704848
 | 
			
		||||
 */
 | 
			
		||||
function search_api_drush_static($function) {
 | 
			
		||||
  static $index = array();
 | 
			
		||||
  if (isset($index[$function])) {
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
  $index[$function] = TRUE;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								search_api.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								search_api.info
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
name = Search API
 | 
			
		||||
description = "Provides a generic API for modules offering search capabilites."
 | 
			
		||||
dependencies[] = entity
 | 
			
		||||
core = 7.x
 | 
			
		||||
package = Search
 | 
			
		||||
 | 
			
		||||
files[] = search_api.test
 | 
			
		||||
files[] = includes/callback.inc
 | 
			
		||||
files[] = includes/callback_add_aggregation.inc
 | 
			
		||||
files[] = includes/callback_add_hierarchy.inc
 | 
			
		||||
files[] = includes/callback_add_url.inc
 | 
			
		||||
files[] = includes/callback_add_viewed_entity.inc
 | 
			
		||||
files[] = includes/callback_bundle_filter.inc
 | 
			
		||||
files[] = includes/callback_language_control.inc
 | 
			
		||||
files[] = includes/callback_node_access.inc
 | 
			
		||||
files[] = includes/callback_node_status.inc
 | 
			
		||||
files[] = includes/datasource.inc
 | 
			
		||||
files[] = includes/datasource_entity.inc
 | 
			
		||||
files[] = includes/datasource_external.inc
 | 
			
		||||
files[] = includes/exception.inc
 | 
			
		||||
files[] = includes/index_entity.inc
 | 
			
		||||
files[] = includes/processor.inc
 | 
			
		||||
files[] = includes/processor_html_filter.inc
 | 
			
		||||
files[] = includes/processor_ignore_case.inc
 | 
			
		||||
files[] = includes/processor_stopwords.inc
 | 
			
		||||
files[] = includes/processor_tokenizer.inc
 | 
			
		||||
files[] = includes/query.inc
 | 
			
		||||
files[] = includes/server_entity.inc
 | 
			
		||||
files[] = includes/service.inc
 | 
			
		||||
 | 
			
		||||
configure = admin/config/search/search_api
 | 
			
		||||
 | 
			
		||||
; Information added by drupal.org packaging script on 2013-01-09
 | 
			
		||||
version = "7.x-1.4"
 | 
			
		||||
core = "7.x"
 | 
			
		||||
project = "search_api"
 | 
			
		||||
datestamp = "1357726719"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										808
									
								
								search_api.install
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										808
									
								
								search_api.install
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,808 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Install, update and uninstall functions for the Search API module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_schema().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_schema() {
 | 
			
		||||
  $schema['search_api_server'] = array(
 | 
			
		||||
    'description' => 'Stores all search servers created through the Search API.',
 | 
			
		||||
    'fields' => array(
 | 
			
		||||
      'id' => array(
 | 
			
		||||
        'description' => 'The primary identifier for a server.',
 | 
			
		||||
        'type' => 'serial',
 | 
			
		||||
        'unsigned' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'name' => array(
 | 
			
		||||
        'description' => 'The displayed name for a server.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'machine_name' => array(
 | 
			
		||||
        'description' => 'The machine name for a server.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'description' => array(
 | 
			
		||||
        'description' => 'The displayed description for a server.',
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
      'class' => array(
 | 
			
		||||
        'description' => 'The id of the service class to use for this server.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'options' => array(
 | 
			
		||||
        'description' => 'The options used to configure the service object.',
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
        'size' => 'medium',
 | 
			
		||||
        'serialize' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'enabled' => array(
 | 
			
		||||
        'description' => 'A flag indicating whether the server is enabled.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'size' => 'tiny',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      'status' => array(
 | 
			
		||||
        'description' => 'The exportable status of the entity.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 0x01,
 | 
			
		||||
        'size' => 'tiny',
 | 
			
		||||
      ),
 | 
			
		||||
      'module' => array(
 | 
			
		||||
        'description' => 'The name of the providing module if the entity has been defined in code.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 255,
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'indexes' => array(
 | 
			
		||||
      'enabled' => array('enabled'),
 | 
			
		||||
    ),
 | 
			
		||||
    'unique keys' => array(
 | 
			
		||||
      'machine_name' => array('machine_name'),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('id'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $schema['search_api_index'] = array(
 | 
			
		||||
    'description' => 'Stores all search indexes on a {search_api_server}.',
 | 
			
		||||
    'fields' => array(
 | 
			
		||||
      'id' => array(
 | 
			
		||||
        'description' => 'An integer identifying the index.',
 | 
			
		||||
        'type' => 'serial',
 | 
			
		||||
        'unsigned' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'name' => array(
 | 
			
		||||
        'description' => 'A name to be displayed for the index.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'machine_name' => array(
 | 
			
		||||
        'description' => 'The machine name of the index.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'description' => array(
 | 
			
		||||
        'description' => "A string describing the index' use to users.",
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
      'server' => array(
 | 
			
		||||
        'description' => 'The {search_api_server}.machine_name with which data should be indexed.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
      'item_type' => array(
 | 
			
		||||
        'description' => 'The type of items stored in this index.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'options' => array(
 | 
			
		||||
        'description' => 'An array of additional arguments configuring this index.',
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
        'size' => 'medium',
 | 
			
		||||
        'serialize' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'enabled' => array(
 | 
			
		||||
        'description' => 'A flag indicating whether this index is enabled.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'size' => 'tiny',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      'read_only' => array(
 | 
			
		||||
        'description' => 'A flag indicating whether to write to this index.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'size' => 'tiny',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 0,
 | 
			
		||||
      ),
 | 
			
		||||
      'status' => array(
 | 
			
		||||
        'description' => 'The exportable status of the entity.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 0x01,
 | 
			
		||||
        'size' => 'tiny',
 | 
			
		||||
      ),
 | 
			
		||||
      'module' => array(
 | 
			
		||||
        'description' => 'The name of the providing module if the entity has been defined in code.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 255,
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'indexes' => array(
 | 
			
		||||
      'item_type' => array('item_type'),
 | 
			
		||||
      'server' => array('server'),
 | 
			
		||||
      'enabled' => array('enabled'),
 | 
			
		||||
    ),
 | 
			
		||||
    'unique keys' => array(
 | 
			
		||||
      'machine_name' => array('machine_name'),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('id'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  $schema['search_api_item'] = array(
 | 
			
		||||
    'description' => 'Stores the items which should be indexed for each index, and their status.',
 | 
			
		||||
    'fields' => array(
 | 
			
		||||
      'item_id' => array(
 | 
			
		||||
        'description' => "The item's entity id (e.g. {node}.nid for nodes).",
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'unsigned' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'index_id' => array(
 | 
			
		||||
        'description' => 'The {search_api_index}.id this item belongs to.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'unsigned' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'changed' => array(
 | 
			
		||||
        'description' => 'Either a flag or a timestamp to indicate if or when the item was changed since it was last indexed.',
 | 
			
		||||
        'type' => 'int',
 | 
			
		||||
        'size' => 'big',
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
        'default' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'indexes' => array(
 | 
			
		||||
      'indexing' => array('index_id', 'changed'),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('item_id', 'index_id'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $schema;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_install().
 | 
			
		||||
 *
 | 
			
		||||
 * Creates a default node index if the module is installed manually.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_install() {
 | 
			
		||||
  // In case the module is installed via an installation profile, a batch is
 | 
			
		||||
  // active and we skip that.
 | 
			
		||||
  if (batch_get()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $name = t('Default node index');
 | 
			
		||||
  $values = array(
 | 
			
		||||
    'name' => $name,
 | 
			
		||||
    'machine_name' => preg_replace('/[^a-z0-9]+/', '_', drupal_strtolower($name)),
 | 
			
		||||
    'description' => t('An automatically created search index for indexing node data. Might be configured to specific needs.'),
 | 
			
		||||
    'server' => NULL,
 | 
			
		||||
    'item_type' => 'node',
 | 
			
		||||
    'options' => array(
 | 
			
		||||
      'cron_limit' => '50',
 | 
			
		||||
      'data_alter_callbacks' => array(
 | 
			
		||||
        'search_api_alter_node_access' => array(
 | 
			
		||||
          'status' => 1,
 | 
			
		||||
          'weight' => '0',
 | 
			
		||||
          'settings' => array(),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      'processors' => array(
 | 
			
		||||
        'search_api_case_ignore' => array(
 | 
			
		||||
          'status' => 1,
 | 
			
		||||
          'weight' => '0',
 | 
			
		||||
          'settings' => array(
 | 
			
		||||
            'strings' => 0,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_html_filter' => array(
 | 
			
		||||
          'status' => 1,
 | 
			
		||||
          'weight' => '10',
 | 
			
		||||
          'settings' => array(
 | 
			
		||||
            'title' => 0,
 | 
			
		||||
            'alt' => 1,
 | 
			
		||||
            'tags' => "h1 = 5\n" .
 | 
			
		||||
                "h2 = 3\n" .
 | 
			
		||||
                "h3 = 2\n" .
 | 
			
		||||
                "strong = 2\n" .
 | 
			
		||||
                "b = 2\n" .
 | 
			
		||||
                "em = 1.5\n" .
 | 
			
		||||
                "u = 1.5",
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_tokenizer' => array(
 | 
			
		||||
          'status' => 1,
 | 
			
		||||
          'weight' => '20',
 | 
			
		||||
          'settings' => array(
 | 
			
		||||
            'spaces' => '[^\\p{L}\\p{N}]',
 | 
			
		||||
            'ignorable' => '[-]',
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      'fields' => array(
 | 
			
		||||
        'type' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
        ),
 | 
			
		||||
        'title' => array(
 | 
			
		||||
          'type' => 'text',
 | 
			
		||||
          'boost' => '5.0',
 | 
			
		||||
        ),
 | 
			
		||||
        'promote' => array(
 | 
			
		||||
          'type' => 'boolean',
 | 
			
		||||
        ),
 | 
			
		||||
        'sticky' => array(
 | 
			
		||||
          'type' => 'boolean',
 | 
			
		||||
        ),
 | 
			
		||||
        'created' => array(
 | 
			
		||||
          'type' => 'date',
 | 
			
		||||
        ),
 | 
			
		||||
        'changed' => array(
 | 
			
		||||
          'type' => 'date',
 | 
			
		||||
        ),
 | 
			
		||||
        'author' => array(
 | 
			
		||||
          'type' => 'integer',
 | 
			
		||||
          'entity_type' => 'user',
 | 
			
		||||
        ),
 | 
			
		||||
        'comment_count' => array(
 | 
			
		||||
          'type' => 'integer',
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_language' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
        ),
 | 
			
		||||
        'body:value' => array(
 | 
			
		||||
          'type' => 'text',
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  search_api_index_insert($values);
 | 
			
		||||
  drupal_set_message('The Search API module was installed. A new default node index was created.');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_enable().
 | 
			
		||||
 *
 | 
			
		||||
 * Mark all items as "dirty", since we can't know whether they are.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_enable() {
 | 
			
		||||
  $types = array();
 | 
			
		||||
  foreach (search_api_index_load_multiple(FALSE) as $index) {
 | 
			
		||||
    if ($index->enabled) {
 | 
			
		||||
      $types[$index->item_type][] = $index;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  foreach ($types as $type => $indexes) {
 | 
			
		||||
    $controller = search_api_get_datasource_controller($type);
 | 
			
		||||
    $controller->startTracking($indexes);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_disable().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_disable() {
 | 
			
		||||
  $types = array();
 | 
			
		||||
  foreach (search_api_index_load_multiple(FALSE) as $index) {
 | 
			
		||||
    $types[$index->item_type][] = $index;
 | 
			
		||||
  }
 | 
			
		||||
  foreach ($types as $type => $indexes) {
 | 
			
		||||
    $controller = search_api_get_datasource_controller($type);
 | 
			
		||||
    $controller->stopTracking($indexes);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_uninstall().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_uninstall() {
 | 
			
		||||
  variable_del('search_api_tasks');
 | 
			
		||||
  variable_del('search_api_index_worker_callback_runtime');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Update function that adds the machine names for servers and indexes.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7101() {
 | 
			
		||||
  $tx = db_transaction();
 | 
			
		||||
  try {
 | 
			
		||||
    // Servers
 | 
			
		||||
    $spec = array(
 | 
			
		||||
      'description' => 'The machine name for a server.',
 | 
			
		||||
      'type' => 'varchar',
 | 
			
		||||
      'length' => 50,
 | 
			
		||||
      'not null' => TRUE,
 | 
			
		||||
      'default' => '',
 | 
			
		||||
    );
 | 
			
		||||
    db_add_field('search_api_server', 'machine_name', $spec);
 | 
			
		||||
 | 
			
		||||
    $names = array();
 | 
			
		||||
    $servers = db_select('search_api_server', 's')
 | 
			
		||||
      ->fields('s')
 | 
			
		||||
      ->execute();
 | 
			
		||||
    foreach ($servers as $server) {
 | 
			
		||||
      $base = $name = drupal_strtolower(preg_replace('/[^a-z0-9]+/i', '_', $server->name));
 | 
			
		||||
      $i = 0;
 | 
			
		||||
      while (isset($names[$name])) {
 | 
			
		||||
        $name = $base . '_' . ++$i;
 | 
			
		||||
      }
 | 
			
		||||
      $names[$name] = TRUE;
 | 
			
		||||
      db_update('search_api_server')
 | 
			
		||||
        ->fields(array('machine_name' => $name))
 | 
			
		||||
        ->condition('id', $server->id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    db_add_unique_key('search_api_server', 'machine_name', array('machine_name'));
 | 
			
		||||
 | 
			
		||||
    //Indexes
 | 
			
		||||
    $spec = array(
 | 
			
		||||
      'description' => 'The machine name of the index.',
 | 
			
		||||
      'type' => 'varchar',
 | 
			
		||||
      'length' => 50,
 | 
			
		||||
      'not null' => TRUE,
 | 
			
		||||
      'default' => '',
 | 
			
		||||
    );
 | 
			
		||||
    db_add_field('search_api_index', 'machine_name', $spec);
 | 
			
		||||
 | 
			
		||||
    $names = array();
 | 
			
		||||
    $indexes = db_select('search_api_index', 'i')
 | 
			
		||||
      ->fields('i')
 | 
			
		||||
      ->execute();
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $base = $name = drupal_strtolower(preg_replace('/[^a-z0-9]+/i', '_', $index->name));
 | 
			
		||||
      $i = 0;
 | 
			
		||||
      while (isset($names[$name])) {
 | 
			
		||||
        $name = $base . '_' . ++$i;
 | 
			
		||||
      }
 | 
			
		||||
      $names[$name] = TRUE;
 | 
			
		||||
      db_update('search_api_index')
 | 
			
		||||
        ->fields(array('machine_name' => $name))
 | 
			
		||||
        ->condition('id', $index->id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    db_add_unique_key('search_api_index', 'machine_name', array('machine_name'));
 | 
			
		||||
  }
 | 
			
		||||
  catch (Exception $e) {
 | 
			
		||||
    $tx->rollback();
 | 
			
		||||
    try {
 | 
			
		||||
      db_drop_field('search_api_server', 'machine_name');
 | 
			
		||||
      db_drop_field('search_api_index', 'machine_name');
 | 
			
		||||
    }
 | 
			
		||||
    catch (Exception $e1) {
 | 
			
		||||
      // Ignore.
 | 
			
		||||
    }
 | 
			
		||||
    throw new DrupalUpdateException(t('An exception occurred during the update: @msg.', array('@msg' => $e->getMessage())));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Update replacing IDs with machine names for foreign keys.
 | 
			
		||||
 * {search_api_index}.server and {search_api_item}.index_id are altered.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7102() {
 | 
			
		||||
  // Update of search_api_index:
 | 
			
		||||
  $indexes = array();
 | 
			
		||||
  $select = db_select('search_api_index', 'i')->fields('i');
 | 
			
		||||
  foreach ($select->execute() as $index) {
 | 
			
		||||
    $indexes[$index->id] = $index;
 | 
			
		||||
  }
 | 
			
		||||
  $servers = db_select('search_api_server', 's')->fields('s', array('id', 'machine_name'))->execute()->fetchAllKeyed();
 | 
			
		||||
 | 
			
		||||
  db_drop_index('search_api_index', 'server');
 | 
			
		||||
  db_drop_field('search_api_index', 'server');
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'The {search_api_server}.machine_name with which data should be indexed.',
 | 
			
		||||
    'type' => 'varchar',
 | 
			
		||||
    'length' => 50,
 | 
			
		||||
    'not null' => FALSE,
 | 
			
		||||
  );
 | 
			
		||||
  db_add_field('search_api_index', 'server', $spec);
 | 
			
		||||
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    db_update('search_api_index')
 | 
			
		||||
      ->fields(array('server' => $servers[$index->server]))
 | 
			
		||||
      ->condition('id', $index->id)
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
  db_add_index('search_api_index', 'server', array('server'));
 | 
			
		||||
 | 
			
		||||
  // Update of search_api_item:
 | 
			
		||||
  db_drop_index('search_api_item', 'indexing');
 | 
			
		||||
  db_drop_primary_key('search_api_item');
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'The {search_api_index}.machine_name this item belongs to.',
 | 
			
		||||
    'type' => 'varchar',
 | 
			
		||||
    'length' => 50,
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
  );
 | 
			
		||||
  $keys_new = array(
 | 
			
		||||
    'indexes' => array(
 | 
			
		||||
      'indexing' => array('index_id', 'changed'),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('item_id', 'index_id'),
 | 
			
		||||
  );
 | 
			
		||||
  db_change_field('search_api_item', 'index_id', 'index_id', $spec, $keys_new);
 | 
			
		||||
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    // We explicitly forbid numeric machine names, therefore we don't have to
 | 
			
		||||
    // worry about conflicts here.
 | 
			
		||||
    db_update('search_api_item')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'index_id' => $index->machine_name,
 | 
			
		||||
      ))
 | 
			
		||||
      ->condition('index_id', $index->id)
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the database fields newly required for entities by the Entity API.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7103() {
 | 
			
		||||
  if (!function_exists('entity_exportable_schema_fields')) {
 | 
			
		||||
    throw new DrupalUpdateException(t('Please update the Entity API module first.'));
 | 
			
		||||
  }
 | 
			
		||||
  foreach (array('search_api_server', 'search_api_index') as $table) {
 | 
			
		||||
    foreach (entity_exportable_schema_fields() as $field => $spec) {
 | 
			
		||||
      db_add_field($table, $field, $spec);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Initialize the "Fields to run on" settings for processors.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7107() {
 | 
			
		||||
  $rows = db_select('search_api_index', 'i')
 | 
			
		||||
    ->fields('i', array('id', 'options'))
 | 
			
		||||
    ->execute()
 | 
			
		||||
    ->fetchAllKeyed();
 | 
			
		||||
  foreach ($rows as $id => $options) {
 | 
			
		||||
    $opt = unserialize($options);
 | 
			
		||||
    $processors = &$opt['processors'];
 | 
			
		||||
    // Only update our own processors, don't mess with others.
 | 
			
		||||
    $check_processors = array(
 | 
			
		||||
      'search_api_case_ignore' => 1,
 | 
			
		||||
      'search_api_html_filter' => 1,
 | 
			
		||||
      'search_api_tokenizer' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    foreach (array_intersect_key($processors, $check_processors) as $name => $info) {
 | 
			
		||||
      $types = array('text');
 | 
			
		||||
      if (!empty($info['settings']['strings'])) {
 | 
			
		||||
        $types[] = 'string';
 | 
			
		||||
        unset($processors[$name]['settings']['strings']);
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($opt['fields'] as $field => $info) {
 | 
			
		||||
        if ($info['indexed'] && search_api_is_text_type($info['type'], $types)) {
 | 
			
		||||
          $processors[$name]['settings']['fields'][$field] = $field;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $opt = serialize($opt);
 | 
			
		||||
    if ($opt != $options) {
 | 
			
		||||
      db_update('search_api_index')
 | 
			
		||||
        ->fields(array(
 | 
			
		||||
          'options' => $opt,
 | 
			
		||||
        ))
 | 
			
		||||
        ->condition('id', $id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change {search_api_item}.index_id back to the index' numeric ID.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7104() {
 | 
			
		||||
  $select = db_select('search_api_index', 'i')->fields('i');
 | 
			
		||||
  foreach ($select->execute() as $index) {
 | 
			
		||||
    // We explicitly forbid numeric machine names, therefore we don't have to
 | 
			
		||||
    // worry about conflicts here.
 | 
			
		||||
    db_update('search_api_item')
 | 
			
		||||
      ->fields(array(
 | 
			
		||||
        'index_id' => $index->id,
 | 
			
		||||
      ))
 | 
			
		||||
      ->condition('index_id', $index->machine_name)
 | 
			
		||||
      ->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Update primary key and index.
 | 
			
		||||
  db_drop_index('search_api_item', 'indexing');
 | 
			
		||||
  db_drop_primary_key('search_api_item');
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'The {search_api_index}.id this item belongs to.',
 | 
			
		||||
    'type' => 'int',
 | 
			
		||||
    'unsigned' => TRUE,
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
  );
 | 
			
		||||
  $keys_new = array(
 | 
			
		||||
    'indexes' => array(
 | 
			
		||||
      'indexing' => array('index_id', 'changed'),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('item_id', 'index_id'),
 | 
			
		||||
  );
 | 
			
		||||
  db_change_field('search_api_item', 'index_id', 'index_id', $spec, $keys_new);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove all empty aggregated fields for the search_api_alter_add_fulltext data
 | 
			
		||||
 * alterations.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7105() {
 | 
			
		||||
  $rows = db_select('search_api_index', 'i')
 | 
			
		||||
    ->fields('i', array('id', 'options'))
 | 
			
		||||
    ->execute()
 | 
			
		||||
    ->fetchAllKeyed();
 | 
			
		||||
  foreach ($rows as $id => $options) {
 | 
			
		||||
    $opt = unserialize($options);
 | 
			
		||||
    if (isset($opt['data_alter_callbacks']['search_api_alter_add_fulltext']['settings']['fields'])) {
 | 
			
		||||
      foreach ($opt['data_alter_callbacks']['search_api_alter_add_fulltext']['settings']['fields'] as $name => $field) {
 | 
			
		||||
        if (empty($field['name']) || empty($field['fields'])) {
 | 
			
		||||
          unset($opt['data_alter_callbacks']['search_api_alter_add_fulltext']['settings']['fields'][$name]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $opt = serialize($opt);
 | 
			
		||||
    if ($opt != $options) {
 | 
			
		||||
      db_update('search_api_index')
 | 
			
		||||
        ->fields(array(
 | 
			
		||||
          'options' => $opt,
 | 
			
		||||
        ))
 | 
			
		||||
        ->condition('id', $id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Update the settings for the "Aggregated fields" data alteration.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7106() {
 | 
			
		||||
  $rows = db_select('search_api_index', 'i')
 | 
			
		||||
    ->fields('i')
 | 
			
		||||
    ->execute()
 | 
			
		||||
    ->fetchAll();
 | 
			
		||||
  foreach ($rows as $row) {
 | 
			
		||||
    $opt = unserialize($row->options);
 | 
			
		||||
    $callbacks = &$opt['data_alter_callbacks'];
 | 
			
		||||
    if (isset($callbacks['search_api_alter_add_fulltext'])) {
 | 
			
		||||
      $callbacks['search_api_alter_add_aggregation'] = $callbacks['search_api_alter_add_fulltext'];
 | 
			
		||||
      unset($callbacks['search_api_alter_add_fulltext']);
 | 
			
		||||
      if (!empty($callbacks['search_api_alter_add_aggregation']['settings']['fields'])) {
 | 
			
		||||
        foreach ($callbacks['search_api_alter_add_aggregation']['settings']['fields'] as $field => &$info) {
 | 
			
		||||
          if (!isset($info['type'])) {
 | 
			
		||||
            $info['type'] = 'fulltext';
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $opt = serialize($opt);
 | 
			
		||||
    if ($opt != $row->options) {
 | 
			
		||||
      // Mark the entity as overridden, in case it has been defined in code
 | 
			
		||||
      // only.
 | 
			
		||||
      $row->status |= 0x01;
 | 
			
		||||
      db_update('search_api_index')
 | 
			
		||||
        ->fields(array(
 | 
			
		||||
          'options' => $opt,
 | 
			
		||||
          'status' => $row->status,
 | 
			
		||||
        ))
 | 
			
		||||
        ->condition('id', $row->id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add "read only" property to Search API index entities.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7108() {
 | 
			
		||||
  $db_field = array(
 | 
			
		||||
    'description' => 'A flag indicating whether to write to this index.',
 | 
			
		||||
    'type' => 'int',
 | 
			
		||||
    'size' => 'tiny',
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
    'default' => 0,
 | 
			
		||||
  );
 | 
			
		||||
  db_add_field('search_api_index', 'read_only', $db_field);
 | 
			
		||||
  return t('Added a "read only" property to index entities.');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clear entity info cache, as entity controller classes hae changed.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7109() {
 | 
			
		||||
  cache_clear_all('entity_info:', 'cache', TRUE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rename the "entity_type" field to "item_type" in the {search_api_index} table.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7110() {
 | 
			
		||||
  $table = 'search_api_index';
 | 
			
		||||
  // This index isn't used anymore.
 | 
			
		||||
  db_drop_index($table, 'entity_type');
 | 
			
		||||
  // Rename the "item_type" field (and change the description).
 | 
			
		||||
  $item_type = array(
 | 
			
		||||
    'description' => 'The type of items stored in this index.',
 | 
			
		||||
    'type' => 'varchar',
 | 
			
		||||
    'length' => 50,
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
  );
 | 
			
		||||
  // Also add the new "item_type" index, while we're at it.
 | 
			
		||||
  $keys_new['indexes']['item_type'] = array('item_type');
 | 
			
		||||
  db_change_field($table, 'entity_type', 'item_type', $item_type, $keys_new);
 | 
			
		||||
  // Mark all indexes in code as "OVERRIDDEN".
 | 
			
		||||
  db_update($table)
 | 
			
		||||
    ->fields(array(
 | 
			
		||||
      'status' => 0x03,
 | 
			
		||||
    ))
 | 
			
		||||
    ->condition('status', 0x02)
 | 
			
		||||
    ->execute();
 | 
			
		||||
  // Clear entity info caches.
 | 
			
		||||
  cache_clear_all('*', 'cache', TRUE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change the definition of the {search_api_item}.changed field.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7111() {
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'Either a flag or a timestamp to indicate if or when the item was changed since it was last indexed.',
 | 
			
		||||
    'type' => 'int',
 | 
			
		||||
    'size' => 'big',
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
    'default' => 1,
 | 
			
		||||
  );
 | 
			
		||||
  db_change_field('search_api_item', 'changed', 'changed', $spec);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Changes the size of the {search_api_index}.options and {search_api_server}.options fields to "medium".
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7112() {
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'The options used to configure the service object.',
 | 
			
		||||
    'type' => 'text',
 | 
			
		||||
    'size' => 'medium',
 | 
			
		||||
    'serialize' => TRUE,
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
  );
 | 
			
		||||
  db_change_field('search_api_server', 'options', 'options', $spec);
 | 
			
		||||
  $spec = array(
 | 
			
		||||
    'description' => 'An array of additional arguments configuring this index.',
 | 
			
		||||
    'type' => 'text',
 | 
			
		||||
    'size' => 'medium',
 | 
			
		||||
    'serialize' => TRUE,
 | 
			
		||||
    'not null' => TRUE,
 | 
			
		||||
  );
 | 
			
		||||
  db_change_field('search_api_index', 'options', 'options', $spec);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Removes superfluous data from the stored index options.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7113() {
 | 
			
		||||
  $indexes = db_select('search_api_index', 'i')
 | 
			
		||||
    ->fields('i')
 | 
			
		||||
    ->execute();
 | 
			
		||||
  foreach ($indexes as $index) {
 | 
			
		||||
    $options = unserialize($index->options);
 | 
			
		||||
    // Weed out fields settings.
 | 
			
		||||
    if (!empty($options['fields'])) {
 | 
			
		||||
      foreach ($options['fields'] as $key => $field) {
 | 
			
		||||
        if (isset($field['indexed']) && !$field['indexed']) {
 | 
			
		||||
          unset($options['fields'][$key]);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        unset($options['fields'][$key]['name'], $options['fields'][$key]['indexed']);
 | 
			
		||||
        if (isset($field['boost']) && $field['boost'] == '1.0') {
 | 
			
		||||
          unset($options['fields'][$key]['boost']);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Weed out processor settings.
 | 
			
		||||
    if (!empty($options['processors'])) {
 | 
			
		||||
      // Only weed out settings for our own processors.
 | 
			
		||||
      $processors = array('search_api_case_ignore', 'search_api_html_filter', 'search_api_tokenizer', 'search_api_stopwords');
 | 
			
		||||
      foreach ($processors as $key) {
 | 
			
		||||
        if (empty($options['processors'][$key])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $processor = $options['processors'][$key];
 | 
			
		||||
        if (empty($processor['settings']['fields'])) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $fields = array_filter($processor['settings']['fields']);
 | 
			
		||||
        if ($fields) {
 | 
			
		||||
          $fields = array_combine($fields, array_fill(0, count($fields), TRUE));
 | 
			
		||||
        }
 | 
			
		||||
        $options['processors'][$key]['settings']['fields'] = $fields;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Weed out settings for the „Aggregated fields“ data alteration.
 | 
			
		||||
    if (!empty($options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'])) {
 | 
			
		||||
      unset($options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['actions']);
 | 
			
		||||
      $aggregated_fields = &$options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'];
 | 
			
		||||
      foreach ($aggregated_fields as $key => $field) {
 | 
			
		||||
        unset($aggregated_fields[$key]['actions']);
 | 
			
		||||
        if (!empty($field['fields'])) {
 | 
			
		||||
          $aggregated_fields[$key]['fields'] = array_values(array_filter($field['fields']));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $options = serialize($options);
 | 
			
		||||
    if ($options != $index->options) {
 | 
			
		||||
      // Mark the entity as overridden, in case it has been defined in code
 | 
			
		||||
      // only.
 | 
			
		||||
      $index->status |= 0x01;
 | 
			
		||||
      db_update('search_api_index')
 | 
			
		||||
        ->fields(array(
 | 
			
		||||
          'options' => $options,
 | 
			
		||||
          'status' => $index->status,
 | 
			
		||||
        ))
 | 
			
		||||
        ->condition('id', $index->id)
 | 
			
		||||
        ->execute();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sanitize watchdog messages.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_update_7114() {
 | 
			
		||||
  if (db_table_exists('watchdog')) {
 | 
			
		||||
    try {
 | 
			
		||||
      $entries = db_select('watchdog', 'w')
 | 
			
		||||
        ->fields('w', array('wid', 'message'))
 | 
			
		||||
        ->condition('type', 'search_api')
 | 
			
		||||
        ->execute();
 | 
			
		||||
      foreach ($entries as $entry) {
 | 
			
		||||
        db_update('watchdog')
 | 
			
		||||
          ->fields(array(
 | 
			
		||||
            'message' => check_plain($entry->message),
 | 
			
		||||
          ))
 | 
			
		||||
          ->condition('wid', $entry->wid)
 | 
			
		||||
          ->execute();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    catch (Exception $e) {
 | 
			
		||||
      throw new DrupalUpdateException(t('An exception occurred during the update: @msg.', array('@msg' => $e->getMessage())));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2352
									
								
								search_api.module
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2352
									
								
								search_api.module
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										90
									
								
								search_api.rules.inc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								search_api.rules.inc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Search API Rules integration.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_rules_action_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_rules_action_info() {
 | 
			
		||||
  $items['search_api_index'] = array (
 | 
			
		||||
   'parameter' => array(
 | 
			
		||||
      'entity' => array(
 | 
			
		||||
        'type' => 'entity',
 | 
			
		||||
        'label' => t('Entity'),
 | 
			
		||||
        'description' => t('The item to index.'),
 | 
			
		||||
      ),
 | 
			
		||||
      'index' => array(
 | 
			
		||||
        'type' => 'search_api_index',
 | 
			
		||||
        'label' => t('Index'),
 | 
			
		||||
        'description' => t('The index on which the item should be indexed. Leave blank to index on all indexes for this item type.'),
 | 
			
		||||
        'optional' => TRUE,
 | 
			
		||||
        'options list' => 'search_api_index_options_list',
 | 
			
		||||
      ),
 | 
			
		||||
      'index_immediately' => array(
 | 
			
		||||
        'type' => 'boolean',
 | 
			
		||||
        'label' => t('Index immediately'),
 | 
			
		||||
        'description' => t('Activate for indexing the item right away, otherwise it will only be marked as dirty and indexed during the next cron run.'),
 | 
			
		||||
        'optional' => TRUE,
 | 
			
		||||
        'default value' => TRUE,
 | 
			
		||||
        'restriction' => 'input',
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'group' => t('Search API'),
 | 
			
		||||
    'access callback' => '_search_api_rules_access',
 | 
			
		||||
    'label' => t('Index an entity'),
 | 
			
		||||
    'base' => '_search_api_rules_action_index',
 | 
			
		||||
  );
 | 
			
		||||
  return $items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rules access callback for search api actions.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_rules_access() {
 | 
			
		||||
  return user_access('administer search_api');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rules action for indexing an item.
 | 
			
		||||
 */
 | 
			
		||||
function _search_api_rules_action_index(EntityDrupalWrapper $wrapper, SearchApiIndex $index = NULL, $index_immediately = TRUE) {
 | 
			
		||||
  $type = $wrapper->type();
 | 
			
		||||
  $item_ids = array($wrapper->getIdentifier());
 | 
			
		||||
 | 
			
		||||
  if (empty($index) && !$index_immediately) {
 | 
			
		||||
    search_api_track_item_change($type, $item_ids);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if ($index) {
 | 
			
		||||
    $indexes = array($index);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $conditions = array(
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'item_type' => $type,
 | 
			
		||||
      'read_only' => 0,
 | 
			
		||||
    );
 | 
			
		||||
    $indexes = search_api_index_load_multiple(FALSE, $conditions);
 | 
			
		||||
    if (!$indexes) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if ($index_immediately) {
 | 
			
		||||
    foreach ($indexes as $index) {
 | 
			
		||||
      $indexed = search_api_index_specific_items_delayed($index, $item_ids);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _search_api_rules_action_index_help() {
 | 
			
		||||
  return t('Queues an item for reindexing. If "index immediately" is disabled then the item will be indexed during the next cron run.');
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										700
									
								
								search_api.test
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										700
									
								
								search_api.test
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,700 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class for testing Search API web functionality.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiWebTest extends DrupalWebTestCase {
 | 
			
		||||
 | 
			
		||||
  protected $server_id;
 | 
			
		||||
  protected $index_id;
 | 
			
		||||
 | 
			
		||||
  protected function assertText($text, $message = '', $group = 'Other') {
 | 
			
		||||
    return parent::assertText($text, $message ? $message : $text, $group);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function drupalGet($path, array $options = array(), array $headers = array()) {
 | 
			
		||||
    $ret = parent::drupalGet($path, $options, $headers);
 | 
			
		||||
    $this->assertResponse(200, t('HTTP code 200 returned.'));
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
 | 
			
		||||
    $ret = parent::drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post);
 | 
			
		||||
    $this->assertResponse(200, t('HTTP code 200 returned.'));
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static function getInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'name' => 'Test search API framework',
 | 
			
		||||
      'description' => 'Tests basic functions of the Search API, like creating, editing and deleting servers and indexes.',
 | 
			
		||||
      'group' => 'Search API',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setUp() {
 | 
			
		||||
    parent::setUp('entity', 'search_api', 'search_api_test');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function testFramework() {
 | 
			
		||||
    $this->drupalLogin($this->drupalCreateUser(array('administer search_api')));
 | 
			
		||||
    // @todo Why is there no default index?
 | 
			
		||||
    //$this->deleteDefaultIndex();
 | 
			
		||||
    $this->insertItems();
 | 
			
		||||
    $this->checkOverview1();
 | 
			
		||||
    $this->createIndex();
 | 
			
		||||
    $this->insertItems(5);
 | 
			
		||||
    $this->createServer();
 | 
			
		||||
    $this->checkOverview2();
 | 
			
		||||
    $this->enableIndex();
 | 
			
		||||
    $this->searchNoResults();
 | 
			
		||||
    $this->indexItems();
 | 
			
		||||
    $this->searchSuccess();
 | 
			
		||||
    $this->editServer();
 | 
			
		||||
    $this->clearIndex();
 | 
			
		||||
    $this->searchNoResults();
 | 
			
		||||
    $this->deleteServer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function deleteDefaultIndex() {
 | 
			
		||||
    $this->drupalPost('admin/config/search/search_api/index/default_node_index/delete', array(), t('Confirm'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function insertItems($offset = 0) {
 | 
			
		||||
    $count = db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField();
 | 
			
		||||
    $this->insertItem(array(
 | 
			
		||||
      'id' => $offset + 1,
 | 
			
		||||
      'title' => 'Title 1',
 | 
			
		||||
      'body' => 'Body text 1.',
 | 
			
		||||
      'type' => 'Item',
 | 
			
		||||
    ));
 | 
			
		||||
    $this->insertItem(array(
 | 
			
		||||
      'id' => $offset + 2,
 | 
			
		||||
      'title' => 'Title 2',
 | 
			
		||||
      'body' => 'Body text 2.',
 | 
			
		||||
      'type' => 'Item',
 | 
			
		||||
    ));
 | 
			
		||||
    $this->insertItem(array(
 | 
			
		||||
      'id' => $offset + 3,
 | 
			
		||||
      'title' => 'Title 3',
 | 
			
		||||
      'body' => 'Body text 3.',
 | 
			
		||||
      'type' => 'Item',
 | 
			
		||||
    ));
 | 
			
		||||
    $this->insertItem(array(
 | 
			
		||||
      'id' => $offset + 4,
 | 
			
		||||
      'title' => 'Title 4',
 | 
			
		||||
      'body' => 'Body text 4.',
 | 
			
		||||
      'type' => 'Page',
 | 
			
		||||
    ));
 | 
			
		||||
    $this->insertItem(array(
 | 
			
		||||
      'id' => $offset + 5,
 | 
			
		||||
      'title' => 'Title 5',
 | 
			
		||||
      'body' => 'Body text 5.',
 | 
			
		||||
      'type' => 'Page',
 | 
			
		||||
    ));
 | 
			
		||||
    $count = db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField() - $count;
 | 
			
		||||
    $this->assertEqual($count, 5, t('@count items inserted.', array('@count' => $count)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function insertItem($values) {
 | 
			
		||||
    $this->drupalPost('search_api_test/insert', $values, t('Save'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function checkOverview1() {
 | 
			
		||||
    // This test fails for no apparent reason for drupal.org test bots.
 | 
			
		||||
    // Commenting them out for now.
 | 
			
		||||
    //$this->drupalGet('admin/config/search/search_api');
 | 
			
		||||
    //$this->assertText(t('There are no search servers or indexes defined yet.'), t('"No servers" message is displayed.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function createIndex() {
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'name' => '',
 | 
			
		||||
      'item_type' => '',
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'description' => 'An index used for testing.',
 | 
			
		||||
      'server' => '',
 | 
			
		||||
      'options[cron_limit]' => 5,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost('admin/config/search/search_api/add_index', $values, t('Create index'));
 | 
			
		||||
    $this->assertText(t('!name field is required.', array('!name' => t('Index name'))));
 | 
			
		||||
    $this->assertText(t('!name field is required.', array('!name' => t('Item type'))));
 | 
			
		||||
 | 
			
		||||
    $this->index_id = $id = 'test_index';
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'name' => 'Search API test index',
 | 
			
		||||
      'machine_name' => $id,
 | 
			
		||||
      'item_type' => 'search_api_test',
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'description' => 'An index used for testing.',
 | 
			
		||||
      'server' => '',
 | 
			
		||||
      'options[cron_limit]' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Create index'));
 | 
			
		||||
 | 
			
		||||
    $this->assertText(t('The index was successfully created. Please set up its indexed fields now.'), t('The index was successfully created.'));
 | 
			
		||||
    $found = strpos($this->getUrl(), 'admin/config/search/search_api/index/' . $id) !== FALSE;
 | 
			
		||||
    $this->assertTrue($found, t('Correct redirect.'));
 | 
			
		||||
    $index = search_api_index_load($id, TRUE);
 | 
			
		||||
    $this->assertEqual($index->name, $values['name'], t('Name correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($index->item_type, $values['item_type'], t('Index item type correctly inserted.'));
 | 
			
		||||
    $this->assertFalse($index->enabled, t('Status correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($index->description, $values['description'], t('Description correctly inserted.'));
 | 
			
		||||
    $this->assertNull($index->server, t('Index server correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($index->options['cron_limit'], $values['options[cron_limit]'], t('Cron batch size correctly inserted.'));
 | 
			
		||||
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'additional[field]' => 'parent',
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost("admin/config/search/search_api/index/$id/fields", $values, t('Add fields'));
 | 
			
		||||
    $this->assertText(t('The available fields were successfully changed.'), t('Successfully added fields.'));
 | 
			
		||||
    $this->assertText('Parent » ID', t('!field displayed.', array('!field' => t('Added fields are'))));
 | 
			
		||||
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'fields[id][type]' => 'integer',
 | 
			
		||||
      'fields[id][boost]' => '1.0',
 | 
			
		||||
      'fields[id][indexed]' => 1,
 | 
			
		||||
      'fields[title][type]' => 'text',
 | 
			
		||||
      'fields[title][boost]' => '5.0',
 | 
			
		||||
      'fields[title][indexed]' => 1,
 | 
			
		||||
      'fields[body][type]' => 'text',
 | 
			
		||||
      'fields[body][boost]' => '1.0',
 | 
			
		||||
      'fields[body][indexed]' => 1,
 | 
			
		||||
      'fields[type][type]' => 'string',
 | 
			
		||||
      'fields[type][boost]' => '1.0',
 | 
			
		||||
      'fields[type][indexed]' => 1,
 | 
			
		||||
      'fields[parent:id][type]' => 'integer',
 | 
			
		||||
      'fields[parent:id][boost]' => '1.0',
 | 
			
		||||
      'fields[parent:id][indexed]' => 1,
 | 
			
		||||
      'fields[parent:title][type]' => 'text',
 | 
			
		||||
      'fields[parent:title][boost]' => '5.0',
 | 
			
		||||
      'fields[parent:title][indexed]' => 1,
 | 
			
		||||
      'fields[parent:body][type]' => 'text',
 | 
			
		||||
      'fields[parent:body][boost]' => '1.0',
 | 
			
		||||
      'fields[parent:body][indexed]' => 1,
 | 
			
		||||
      'fields[parent:type][type]' => 'string',
 | 
			
		||||
      'fields[parent:type][boost]' => '1.0',
 | 
			
		||||
      'fields[parent:type][indexed]' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Save changes'));
 | 
			
		||||
    $this->assertText(t('The indexed fields were successfully changed. The index was cleared and will have to be re-indexed with the new settings.'), t('Field settings saved.'));
 | 
			
		||||
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'callbacks[search_api_alter_add_url][status]' => 1,
 | 
			
		||||
      'callbacks[search_api_alter_add_url][weight]' => 0,
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][status]' => 1,
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][weight]' => 10,
 | 
			
		||||
      'processors[search_api_case_ignore][status]' => 1,
 | 
			
		||||
      'processors[search_api_case_ignore][weight]' => 0,
 | 
			
		||||
      'processors[search_api_case_ignore][settings][fields][title]' => 1,
 | 
			
		||||
      'processors[search_api_case_ignore][settings][fields][body]' => 1,
 | 
			
		||||
      'processors[search_api_case_ignore][settings][fields][parent:title]' => 1,
 | 
			
		||||
      'processors[search_api_case_ignore][settings][fields][parent:body]' => 1,
 | 
			
		||||
      'processors[search_api_tokenizer][status]' => 1,
 | 
			
		||||
      'processors[search_api_tokenizer][weight]' => 20,
 | 
			
		||||
      'processors[search_api_tokenizer][settings][spaces]' => '[^\p{L}\p{N}]',
 | 
			
		||||
      'processors[search_api_tokenizer][settings][ignorable]' => '[-]',
 | 
			
		||||
      'processors[search_api_tokenizer][settings][fields][title]' => 1,
 | 
			
		||||
      'processors[search_api_tokenizer][settings][fields][body]' => 1,
 | 
			
		||||
      'processors[search_api_tokenizer][settings][fields][parent:title]' => 1,
 | 
			
		||||
      'processors[search_api_tokenizer][settings][fields][parent:body]' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Add new field'));
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][name]' => 'Test fulltext field',
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][type]' => 'fulltext',
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][fields][title]' => 1,
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][fields][body]' => 1,
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][fields][parent:title]' => 1,
 | 
			
		||||
      'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][fields][parent:body]' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Save configuration'));
 | 
			
		||||
    $this->assertText(t("The search index' workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."), t('Workflow successfully edited.'));
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet("admin/config/search/search_api/index/$id");
 | 
			
		||||
    $this->assertTitle('Search API test index | Drupal', t('Correct title when viewing index.'));
 | 
			
		||||
    $this->assertText('An index used for testing.', t('!field displayed.', array('!field' => t('Description'))));
 | 
			
		||||
    $this->assertText('Search API test entity', t('!field displayed.', array('!field' => t('Item type'))));
 | 
			
		||||
    $this->assertText(format_plural(1, '1 item per cron batch.', '@count items per cron batch.'), t('!field displayed.', array('!field' => t('Cron batch size'))));
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet("admin/config/search/search_api/index/$id/status");
 | 
			
		||||
    $this->assertText(t('The index is currently disabled.'), t('"Disabled" status displayed.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function createServer() {
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'name' => '',
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'description' => 'A server used for testing.',
 | 
			
		||||
      'class' => '',
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost('admin/config/search/search_api/add_server', $values, t('Create server'));
 | 
			
		||||
    $this->assertText(t('!name field is required.', array('!name' => t('Server name'))));
 | 
			
		||||
    $this->assertText(t('!name field is required.', array('!name' => t('Service class'))));
 | 
			
		||||
 | 
			
		||||
    $this->server_id = $id = 'test_server';
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'name' => 'Search API test server',
 | 
			
		||||
      'machine_name' => $id,
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'description' => 'A server used for testing.',
 | 
			
		||||
      'class' => 'search_api_test_service',
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Create server'));
 | 
			
		||||
 | 
			
		||||
    $values2 = array(
 | 
			
		||||
      'options[form][test]' => 'search_api_test foo bar',
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values2, t('Create server'));
 | 
			
		||||
 | 
			
		||||
    $this->assertText(t('The server was successfully created.'));
 | 
			
		||||
    $found = strpos($this->getUrl(), 'admin/config/search/search_api/server/' . $id) !== FALSE;
 | 
			
		||||
    $this->assertTrue($found, t('Correct redirect.'));
 | 
			
		||||
    $server = search_api_server_load($id, TRUE);
 | 
			
		||||
    $this->assertEqual($server->name, $values['name'], t('Name correctly inserted.'));
 | 
			
		||||
    $this->assertTrue($server->enabled, t('Status correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($server->description, $values['description'], t('Description correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($server->class, $values['class'], t('Service class correctly inserted.'));
 | 
			
		||||
    $this->assertEqual($server->options['test'], $values2['options[form][test]'], t('Service options correctly inserted.'));
 | 
			
		||||
    $this->assertTitle('Search API test server | Drupal', t('Correct title when viewing server.'));
 | 
			
		||||
    $this->assertText('A server used for testing.', t('!field displayed.', array('!field' => t('Description'))));
 | 
			
		||||
    $this->assertText('search_api_test_service', t('!field displayed.', array('!field' => t('Service name'))));
 | 
			
		||||
    $this->assertText('search_api_test_service description', t('!field displayed.', array('!field' => t('Service description'))));
 | 
			
		||||
    $this->assertText('search_api_test foo bar', t('!field displayed.', array('!field' => t('Service options'))));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function checkOverview2() {
 | 
			
		||||
    $this->drupalGet('admin/config/search/search_api');
 | 
			
		||||
    $this->assertText('Search API test server', t('!field displayed.', array('!field' => t('Server'))));
 | 
			
		||||
    $this->assertText('Search API test index', t('!field displayed.', array('!field' => t('Index'))));
 | 
			
		||||
    $this->assertNoText(t('There are no search servers or indexes defined yet.'), t('"No servers" message not displayed.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function enableIndex() {
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'server' => $this->server_id,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost("admin/config/search/search_api/index/{$this->index_id}/edit", $values, t('Save settings'));
 | 
			
		||||
    $this->assertText(t('The search index was successfully edited.'));
 | 
			
		||||
    $this->assertText('Search API test server', t('!field displayed.', array('!field' => t('Server'))));
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet("admin/config/search/search_api/index/{$this->index_id}/enable");
 | 
			
		||||
    $this->assertText(t('The index was successfully enabled.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function searchNoResults() {
 | 
			
		||||
    $this->drupalGet('search_api_test/query/' . $this->index_id);
 | 
			
		||||
    $this->assertText('result count = 0', t('No search results returned without indexing.'));
 | 
			
		||||
    $this->assertText('results = ()', t('No search results returned without indexing.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function indexItems() {
 | 
			
		||||
    $this->drupalGet("admin/config/search/search_api/index/{$this->index_id}/status");
 | 
			
		||||
    $this->assertText(t('The index is currently enabled.'), t('"Enabled" status displayed.'));
 | 
			
		||||
    $this->assertText(t('All items still need to be indexed (@total total).', array('@total' => 10)), t('!field displayed.', array('!field' => t('Correct index status'))));
 | 
			
		||||
    $this->assertText(t('Index now'), t('"Index now" button found.'));
 | 
			
		||||
    $this->assertText(t('Clear index'), t('"Clear index" button found.'));
 | 
			
		||||
    $this->assertNoText(t('Re-index content'), t('"Re-index" button not found.'));
 | 
			
		||||
 | 
			
		||||
    // Here we test the indexing + the warning message when some items
 | 
			
		||||
    // can not be indexed.
 | 
			
		||||
    // The server refuses (for test purpose) to index items with IDs that are
 | 
			
		||||
    // multiples of 8 unless the "search_api_test_index_all" variable is set.
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'limit' => 8,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Index now'));
 | 
			
		||||
    $this->assertText(t('Successfully indexed @count items.', array('@count' => 7)));
 | 
			
		||||
    $this->assertText(t('1 item could not be indexed. Check the logs for details.'), t('Index errors warning is displayed.'));
 | 
			
		||||
    $this->assertNoText(t("Couldn't index items. Check the logs for details."), t("Index error isn't displayed."));
 | 
			
		||||
    $this->assertText(t('About @percentage% of all items have been indexed in their latest version (@indexed / @total).', array('@indexed' => 7, '@total' => 10, '@percentage' => 70)), t('!field displayed.', array('!field' => t('Correct index status'))));
 | 
			
		||||
    $this->assertText(t('Re-indexing'), t('"Re-index" button found.'));
 | 
			
		||||
 | 
			
		||||
    // Here we're testing the error message when no item could be indexed.
 | 
			
		||||
    // The item with ID 8 is still not indexed.
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'limit' => 1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Index now'));
 | 
			
		||||
    $this->assertNoPattern('/' . str_replace('144', '-?\d*', t('Successfully indexed @count items.', array('@count' => 144))) . '/', t('No items could be indexed.'));
 | 
			
		||||
    $this->assertNoText(t('1 item could not be indexed. Check the logs for details.'), t("Index errors warning isn't displayed."));
 | 
			
		||||
    $this->assertText(t("Couldn't index items. Check the logs for details."), t('Index error is displayed.'));
 | 
			
		||||
 | 
			
		||||
    // Here we test the indexing of all the remaining items.
 | 
			
		||||
    variable_set('search_api_test_index_all', TRUE);
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'limit' => -1,
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost(NULL, $values, t('Index now'));
 | 
			
		||||
    $this->assertText(t('Successfully indexed @count items.', array('@count' => 3)));
 | 
			
		||||
    $this->assertNoText(t("Some items couldn't be indexed. Check the logs for details."), t("Index errors warning isn't displayed."));
 | 
			
		||||
    $this->assertNoText(t("Couldn't index items. Check the logs for details."), t("Index error isn't displayed."));
 | 
			
		||||
    $this->assertText(t('All items have been indexed (@indexed / @total).', array('@indexed' => 10, '@total' => 10)), t('!field displayed.', array('!field' => t('Correct index status'))));
 | 
			
		||||
    $this->assertNoText(t('Index now'), t('"Index now" button no longer displayed.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function searchSuccess() {
 | 
			
		||||
    $this->drupalGet('search_api_test/query/' . $this->index_id);
 | 
			
		||||
    $this->assertText('result count = 10', t('Correct search result count returned after indexing.'));
 | 
			
		||||
    $this->assertText('results = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)', t('Correct search results returned after indexing.'));
 | 
			
		||||
 | 
			
		||||
    $this->drupalGet('search_api_test/query/' . $this->index_id . '/foo/2/4');
 | 
			
		||||
    $this->assertText('result count = 10', t('Correct search result count with ranged query.'));
 | 
			
		||||
    $this->assertText('results = (3, 4, 5, 6)', t('Correct search results with ranged query.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function editServer() {
 | 
			
		||||
    $values = array(
 | 
			
		||||
      'name' => 'test-name-foo',
 | 
			
		||||
      'description' => 'test-description-bar',
 | 
			
		||||
      'options[form][test]' => 'test-test-baz',
 | 
			
		||||
    );
 | 
			
		||||
    $this->drupalPost("admin/config/search/search_api/server/{$this->server_id}/edit", $values, t('Save settings'));
 | 
			
		||||
    $this->assertText(t('The search server was successfully edited.'));
 | 
			
		||||
    $this->assertText('test-name-foo', t('!field changed.', array('!field' => t('Name'))));
 | 
			
		||||
    $this->assertText('test-description-bar', t('!field changed.', array('!field' => t('Description'))));
 | 
			
		||||
    $this->assertText('test-test-baz', t('!field changed.', array('!field' => t('Service options'))));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function clearIndex() {
 | 
			
		||||
    $this->drupalPost("admin/config/search/search_api/index/{$this->index_id}/status", array(), t('Clear index'));
 | 
			
		||||
    $this->assertText(t('The index was successfully cleared.'));
 | 
			
		||||
    $this->assertText(t('All items still need to be indexed (@total total).', array('@total' => 10)), t('!field displayed.', array('!field' => t('Correct index status'))));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function deleteServer() {
 | 
			
		||||
    $this->drupalPost("admin/config/search/search_api/server/{$this->server_id}/delete", array(), t('Confirm'));
 | 
			
		||||
    $this->assertNoText('test-name-foo', t('Server no longer listed.'));
 | 
			
		||||
    $this->drupalGet("admin/config/search/search_api/index/{$this->index_id}/status");
 | 
			
		||||
    $this->assertText(t('The index is currently disabled.'), t('The index was disabled and removed from the server.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class with unit tests testing small fragments of the Search API.
 | 
			
		||||
 *
 | 
			
		||||
 * Due to severe limitations for "real" unit tests, this still has to be a
 | 
			
		||||
 * subclass of DrupalWebTestCase.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiUnitTest extends DrupalWebTestCase {
 | 
			
		||||
 | 
			
		||||
  protected $index;
 | 
			
		||||
 | 
			
		||||
  protected function assertEqual($first, $second, $message = '', $group = 'Other') {
 | 
			
		||||
    if (is_array($first) && is_array($second)) {
 | 
			
		||||
      return $this->assertTrue($this->deepEquals($first, $second), $message, $group);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return parent::assertEqual($first, $second, $message, $group);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function deepEquals($first, $second) {
 | 
			
		||||
    if (!is_array($first) || !is_array($second)) {
 | 
			
		||||
      return $first == $second;
 | 
			
		||||
    }
 | 
			
		||||
    $first  = array_merge($first);
 | 
			
		||||
    $second = array_merge($second);
 | 
			
		||||
    foreach ($first as $key => $value) {
 | 
			
		||||
      if (!array_key_exists($key, $second) || !$this->deepEquals($value, $second[$key])) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      unset($second[$key]);
 | 
			
		||||
    }
 | 
			
		||||
    return empty($second);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static function getInfo() {
 | 
			
		||||
    return array(
 | 
			
		||||
      'name' => 'Test search API components',
 | 
			
		||||
      'description' => 'Tests some independent components of the Search API, like the processors.',
 | 
			
		||||
      'group' => 'Search API',
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setUp() {
 | 
			
		||||
    parent::setUp('entity', 'search_api');
 | 
			
		||||
    $this->index = entity_create('search_api_index', array(
 | 
			
		||||
      'id' => 1,
 | 
			
		||||
      'name' => 'test',
 | 
			
		||||
      'enabled' => 1,
 | 
			
		||||
      'item_type' => 'user',
 | 
			
		||||
      'options' => array(
 | 
			
		||||
        'fields' => array(
 | 
			
		||||
          'name' => array(
 | 
			
		||||
            'type' => 'text',
 | 
			
		||||
          ),
 | 
			
		||||
          'mail' => array(
 | 
			
		||||
            'type' => 'string',
 | 
			
		||||
          ),
 | 
			
		||||
          'search_api_language' => array(
 | 
			
		||||
            'type' => 'string',
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function testUnits() {
 | 
			
		||||
    $this->checkQueryParseKeys();
 | 
			
		||||
    $this->checkIgnoreCaseProcessor();
 | 
			
		||||
    $this->checkTokenizer();
 | 
			
		||||
    $this->checkHtmlFilter();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function checkQueryParseKeys() {
 | 
			
		||||
    $options['parse mode'] = 'direct';
 | 
			
		||||
    $mode = &$options['parse mode'];
 | 
			
		||||
    $num = 1;
 | 
			
		||||
    $query = new SearchApiQuery($this->index, $options);
 | 
			
		||||
    $modes = $query->parseModes();
 | 
			
		||||
 | 
			
		||||
    $query->keys('foo');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), 'foo', t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('foo bar');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), 'foo bar', t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('(foo bar) OR "bar baz"');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), '(foo bar) OR "bar baz"', t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
 | 
			
		||||
    $mode = 'single';
 | 
			
		||||
    $num = 1;
 | 
			
		||||
    $query = new SearchApiQuery($this->index, $options);
 | 
			
		||||
 | 
			
		||||
    $query->keys('foo');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'foo'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('foo bar');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'foo bar'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('(foo bar) OR "bar baz"');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', '(foo bar) OR "bar baz"'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
 | 
			
		||||
    $mode = 'terms';
 | 
			
		||||
    $num = 1;
 | 
			
		||||
    $query = new SearchApiQuery($this->index, $options);
 | 
			
		||||
 | 
			
		||||
    $query->keys('foo');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'foo'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('foo bar');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'foo', 'bar'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    $query->keys('(foo bar) OR "bar baz"');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('(foo', 'bar)', 'OR', 'bar baz', '#conjunction' => 'AND'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
    // http://drupal.org/node/1468678
 | 
			
		||||
    $query->keys('"Münster"');
 | 
			
		||||
    $this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'Münster'), t('"@mode" parse mode, test !num.', array('@mode' => $modes[$mode]['name'], '!num' => $num++)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function checkIgnoreCaseProcessor() {
 | 
			
		||||
    $types = search_api_field_types();
 | 
			
		||||
    $orig = 'Foo bar BaZ, ÄÖÜÀÁ<>»«.';
 | 
			
		||||
    $processed = drupal_strtolower($orig);
 | 
			
		||||
    $items = array(
 | 
			
		||||
      1 => array(
 | 
			
		||||
        'name' => array(
 | 
			
		||||
          'type' => 'text',
 | 
			
		||||
          'original_type' => 'text',
 | 
			
		||||
          'value' => $orig,
 | 
			
		||||
        ),
 | 
			
		||||
        'mail' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
          'original_type' => 'text',
 | 
			
		||||
          'value' => $orig,
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_language' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
          'original_type' => 'string',
 | 
			
		||||
          'value' => LANGUAGE_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    $keys1 = $keys2 = array(
 | 
			
		||||
      'foo',
 | 
			
		||||
      'bar baz',
 | 
			
		||||
      'foobar1',
 | 
			
		||||
      '#conjunction' => 'AND',
 | 
			
		||||
    );
 | 
			
		||||
    $filters1 = array(
 | 
			
		||||
      array('name', 'foo', '='),
 | 
			
		||||
      array('mail', 'BAR', '='),
 | 
			
		||||
    );
 | 
			
		||||
    $filters2 = array(
 | 
			
		||||
      array('name', 'foo', '='),
 | 
			
		||||
      array('mail', 'bar', '='),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $processor = new SearchApiIgnoreCase($this->index, array('fields' => array('name' => 'name')));
 | 
			
		||||
    $tmp = $items;
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $this->assertEqual($tmp[1]['name']['value'], $processed, t('!type field was processed.', array('!type' => 'name')));
 | 
			
		||||
    $this->assertEqual($tmp[1]['mail']['value'], $orig, t("!type field wasn't processed.", array('!type' => 'mail')));
 | 
			
		||||
 | 
			
		||||
    $query = new SearchApiQuery($this->index);
 | 
			
		||||
    $query->keys('Foo "baR BaZ" fOObAr1');
 | 
			
		||||
    $query->condition('name', 'FOO');
 | 
			
		||||
    $query->condition('mail', 'BAR');
 | 
			
		||||
    $processor->preprocessSearchQuery($query);
 | 
			
		||||
    $this->assertEqual($query->getKeys(), $keys1, t('Search keys were processed correctly.'));
 | 
			
		||||
    $this->assertEqual($query->getFilter()->getFilters(), $filters1, t('Filters were processed correctly.'));
 | 
			
		||||
 | 
			
		||||
    $processor = new SearchApiIgnoreCase($this->index, array('fields' => array('name' => 'name', 'mail' => 'mail')));
 | 
			
		||||
    $tmp = $items;
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $this->assertEqual($tmp[1]['name']['value'], $processed, t('!type field was processed.', array('!type' => 'name')));
 | 
			
		||||
    $this->assertEqual($tmp[1]['mail']['value'], $processed, t('!type field was processed.', array('!type' => 'mail')));
 | 
			
		||||
 | 
			
		||||
    $query = new SearchApiQuery($this->index);
 | 
			
		||||
    $query->keys('Foo "baR BaZ" fOObAr1');
 | 
			
		||||
    $query->condition('name', 'FOO');
 | 
			
		||||
    $query->condition('mail', 'BAR');
 | 
			
		||||
    $processor->preprocessSearchQuery($query);
 | 
			
		||||
    $this->assertEqual($query->getKeys(), $keys2, t('Search keys were processed correctly.'));
 | 
			
		||||
    $this->assertEqual($query->getFilter()->getFilters(), $filters2, t('Filters were processed correctly.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function checkTokenizer() {
 | 
			
		||||
    $orig = 'Foo bar1 BaZ,  La-la-la.';
 | 
			
		||||
    $processed1 = array(
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'Foo',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'bar1',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'BaZ',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'Lalala',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    $processed2 = array(
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'Foob',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'r1B',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'Z,L',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'l',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => 'l',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        'value' => '.',
 | 
			
		||||
        'score' => 1,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    $items = array(
 | 
			
		||||
      1 => array(
 | 
			
		||||
        'name' => array(
 | 
			
		||||
          'type' => 'text',
 | 
			
		||||
          'original_type' => 'text',
 | 
			
		||||
          'value' => $orig,
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_language' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
          'original_type' => 'string',
 | 
			
		||||
          'value' => LANGUAGE_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $processor = new SearchApiTokenizer($this->index, array('fields' => array('name' => 'name'), 'spaces' => '[^\p{L}\p{N}]', 'ignorable' => '[-]'));
 | 
			
		||||
    $tmp = $items;
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $this->assertEqual($tmp[1]['name']['value'], $processed1, t('Value was correctly tokenized with default settings.'));
 | 
			
		||||
 | 
			
		||||
    $query = new SearchApiQuery($this->index, array('parse mode' => 'direct'));
 | 
			
		||||
    $query->keys("foo \"bar-baz\" \n\t foobar1");
 | 
			
		||||
    $processor->preprocessSearchQuery($query);
 | 
			
		||||
    $this->assertEqual($query->getKeys(), 'foo barbaz foobar1', t('Search keys were processed correctly.'));
 | 
			
		||||
 | 
			
		||||
    $processor = new SearchApiTokenizer($this->index, array('fields' => array('name' => 'name'), 'spaces' => '[-a]', 'ignorable' => '\s'));
 | 
			
		||||
    $tmp = $items;
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $this->assertEqual($tmp[1]['name']['value'], $processed2, t('Value was correctly tokenized with custom settings.'));
 | 
			
		||||
 | 
			
		||||
    $query = new SearchApiQuery($this->index, array('parse mode' => 'direct'));
 | 
			
		||||
    $query->keys("foo \"bar-baz\" \n\t foobar1");
 | 
			
		||||
    $processor->preprocessSearchQuery($query);
 | 
			
		||||
    $this->assertEqual($query->getKeys(), 'foo"b r b z"foob r1', t('Search keys were processed correctly.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function checkHtmlFilter() {
 | 
			
		||||
    $orig = <<<END
 | 
			
		||||
This is <em lang="en" title =
 | 
			
		||||
"something">a test</em>.
 | 
			
		||||
How to write <strong>links to <em>other sites</em></strong>: <a href="URL" title="MOUSEOVER TEXT">TEXT</a>.
 | 
			
		||||
< signs can be <A HREF="http://example.com/topic/html-escapes" TITLE =  'HTML "escapes"'
 | 
			
		||||
TARGET = '_blank'>escaped</A> with "&lt;".
 | 
			
		||||
<img src = "foo.png" alt = "someone's image" />
 | 
			
		||||
END;
 | 
			
		||||
    $tags = <<<END
 | 
			
		||||
em = 1.5
 | 
			
		||||
strong = 2
 | 
			
		||||
END;
 | 
			
		||||
    $processed1 = array(
 | 
			
		||||
      array('value' => 'This', 'score' => 1),
 | 
			
		||||
      array('value' => 'is', 'score' => 1),
 | 
			
		||||
      array('value' => 'something', 'score' => 1.5),
 | 
			
		||||
      array('value' => 'a', 'score' => 1.5),
 | 
			
		||||
      array('value' => 'test', 'score' => 1.5),
 | 
			
		||||
      array('value' => 'How', 'score' => 1),
 | 
			
		||||
      array('value' => 'to', 'score' => 1),
 | 
			
		||||
      array('value' => 'write', 'score' => 1),
 | 
			
		||||
      array('value' => 'links', 'score' => 2),
 | 
			
		||||
      array('value' => 'to', 'score' => 2),
 | 
			
		||||
      array('value' => 'other', 'score' => 3),
 | 
			
		||||
      array('value' => 'sites', 'score' => 3),
 | 
			
		||||
      array('value' => '<a', 'score' => 1),
 | 
			
		||||
      array('value' => 'href="URL"', 'score' => 1),
 | 
			
		||||
      array('value' => 'title="MOUSEOVER', 'score' => 1),
 | 
			
		||||
      array('value' => 'TEXT">TEXT</a>', 'score' => 1),
 | 
			
		||||
      array('value' => '<', 'score' => 1),
 | 
			
		||||
      array('value' => 'signs', 'score' => 1),
 | 
			
		||||
      array('value' => 'can', 'score' => 1),
 | 
			
		||||
      array('value' => 'be', 'score' => 1),
 | 
			
		||||
      array('value' => 'HTML', 'score' => 1),
 | 
			
		||||
      array('value' => '"escapes"', 'score' => 1),
 | 
			
		||||
      array('value' => 'escaped', 'score' => 1),
 | 
			
		||||
      array('value' => 'with', 'score' => 1),
 | 
			
		||||
      array('value' => '"<"', 'score' => 1),
 | 
			
		||||
      array('value' => 'someone\'s', 'score' => 1),
 | 
			
		||||
      array('value' => 'image', 'score' => 1),
 | 
			
		||||
    );
 | 
			
		||||
    $items = array(
 | 
			
		||||
      1 => array(
 | 
			
		||||
        'name' => array(
 | 
			
		||||
          'type' => 'text',
 | 
			
		||||
          'original_type' => 'text',
 | 
			
		||||
          'value' => $orig,
 | 
			
		||||
        ),
 | 
			
		||||
        'search_api_language' => array(
 | 
			
		||||
          'type' => 'string',
 | 
			
		||||
          'original_type' => 'string',
 | 
			
		||||
          'value' => LANGUAGE_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $tmp = $items;
 | 
			
		||||
    $processor = new SearchApiHtmlFilter($this->index, array('fields' => array('name' => 'name'), 'title' => TRUE, 'alt' => TRUE, 'tags' => $tags));
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $processor = new SearchApiTokenizer($this->index, array('fields' => array('name' => 'name'), 'spaces' => '[\s.:]', 'ignorable' => ''));
 | 
			
		||||
    $processor->preprocessIndexItems($tmp);
 | 
			
		||||
    $this->assertEqual($tmp[1]['name']['value'], $processed1, t('Text was correctly processed.'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								tests/search_api_test.info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tests/search_api_test.info
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
 | 
			
		||||
name = Search API test
 | 
			
		||||
description = "Some dummy implementations for testing the Search API."
 | 
			
		||||
core = 7.x
 | 
			
		||||
package = Search
 | 
			
		||||
 | 
			
		||||
dependencies[] = search_api
 | 
			
		||||
 | 
			
		||||
files[] = search_api_test.module
 | 
			
		||||
 | 
			
		||||
hidden = TRUE
 | 
			
		||||
 | 
			
		||||
; Information added by drupal.org packaging script on 2013-01-09
 | 
			
		||||
version = "7.x-1.4"
 | 
			
		||||
core = "7.x"
 | 
			
		||||
project = "search_api"
 | 
			
		||||
datestamp = "1357726719"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								tests/search_api_test.install
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								tests/search_api_test.install
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Install, update and uninstall functions for the Search API test module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_schema().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_schema() {
 | 
			
		||||
  $schema['search_api_test'] = array(
 | 
			
		||||
    'description' => 'Stores instances of a test entity.',
 | 
			
		||||
    'fields' => array(
 | 
			
		||||
      'id' => array(
 | 
			
		||||
        'description' => 'The primary identifier for an item.',
 | 
			
		||||
        'type' => 'serial',
 | 
			
		||||
        'unsigned' => TRUE,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'title' => array(
 | 
			
		||||
        'description' => 'The title of the item.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
      'body' => array(
 | 
			
		||||
        'description' => 'A text belonging to the item.',
 | 
			
		||||
        'type' => 'text',
 | 
			
		||||
        'not null' => FALSE,
 | 
			
		||||
      ),
 | 
			
		||||
      'type' => array(
 | 
			
		||||
        'description' => 'A string identifying the type of item.',
 | 
			
		||||
        'type' => 'varchar',
 | 
			
		||||
        'length' => 50,
 | 
			
		||||
        'not null' => TRUE,
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
    'primary key' => array('id'),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $schema;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										316
									
								
								tests/search_api_test.module
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								tests/search_api_test.module
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_menu().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_menu() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'search_api_test/insert' => array(
 | 
			
		||||
      'title' => 'Insert item',
 | 
			
		||||
      'page callback' => 'drupal_get_form',
 | 
			
		||||
      'page arguments' => array('search_api_test_insert_item'),
 | 
			
		||||
      'access callback' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
    'search_api_test/%search_api_test' => array(
 | 
			
		||||
      'title' => 'View item',
 | 
			
		||||
      'page callback' => 'search_api_test_view',
 | 
			
		||||
      'page arguments' => array(1),
 | 
			
		||||
      'access callback' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
    'search_api_test/query/%search_api_index' => array(
 | 
			
		||||
      'title' => 'Search query',
 | 
			
		||||
      'page callback' => 'search_api_test_query',
 | 
			
		||||
      'page arguments' => array(2),
 | 
			
		||||
      'access callback' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form callback for inserting an item.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_insert_item(array $form, array &$form_state) {
 | 
			
		||||
  return array(
 | 
			
		||||
    'id' => array(
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
    ),
 | 
			
		||||
    'title' => array(
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
    ),
 | 
			
		||||
    'body' => array(
 | 
			
		||||
      '#type' => 'textarea',
 | 
			
		||||
    ),
 | 
			
		||||
    'type' => array(
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
    ),
 | 
			
		||||
    'submit' => array(
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => t('Save'),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Submit callback for search_api_test_insert_item().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_insert_item_submit(array $form, array &$form_state) {
 | 
			
		||||
  form_state_values_clean($form_state);
 | 
			
		||||
  db_insert('search_api_test')->fields($form_state['values'])->execute();
 | 
			
		||||
  module_invoke_all('entity_insert', search_api_test_load($form_state['values']['id']), 'search_api_test');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Load handler for search_api_test entities.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_load($id) {
 | 
			
		||||
  $ret = entity_load('search_api_test', array($id));
 | 
			
		||||
  return $ret ? array_shift($ret) : NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Menu callback for displaying search_api_test entities.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_view($entity) {
 | 
			
		||||
  return array('text' => nl2br(check_plain(print_r($entity, TRUE))));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Menu callback for executing a search.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_query(SearchApiIndex $index, $keys = 'foo bar', $offset = 0, $limit = 10, $fields = NULL, $sort = NULL, $filters = NULL) {
 | 
			
		||||
  // Slight "hack" for testing complex queries.
 | 
			
		||||
  if ($keys == '|COMPLEX|') {
 | 
			
		||||
    $keys = array(
 | 
			
		||||
      '#conjunction' => 'AND',
 | 
			
		||||
      'test',
 | 
			
		||||
      array(
 | 
			
		||||
        '#conjunction' => 'OR',
 | 
			
		||||
        'baz',
 | 
			
		||||
        'foobar',
 | 
			
		||||
      ),
 | 
			
		||||
      array(
 | 
			
		||||
        '#conjunction' => 'AND',
 | 
			
		||||
        '#negation' => TRUE,
 | 
			
		||||
        'bar',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  $query = $index->query()
 | 
			
		||||
    ->keys($keys)
 | 
			
		||||
    ->range($offset, $limit);
 | 
			
		||||
  if ($fields) {
 | 
			
		||||
    $query->fields(explode(',', $fields));
 | 
			
		||||
  }
 | 
			
		||||
  if ($sort) {
 | 
			
		||||
    $sort = explode(',', $sort);
 | 
			
		||||
    $query->sort($sort[0], $sort[1]);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $query->sort('search_api_id', 'ASC');
 | 
			
		||||
  }
 | 
			
		||||
  if ($filters) {
 | 
			
		||||
    $filters = explode(',', $filters);
 | 
			
		||||
    foreach ($filters as $filter) {
 | 
			
		||||
      $filter = explode('=', $filter);
 | 
			
		||||
      $query->condition($filter[0], $filter[1]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  $result = $query->execute();
 | 
			
		||||
 | 
			
		||||
  $ret = '';
 | 
			
		||||
  $ret .= 'result count = ' . (int) $result['result count'] . '<br/>';
 | 
			
		||||
  $ret .= 'results = (' . (empty($result['results']) ? '' : implode(', ', array_keys($result['results']))) . ')<br/>';
 | 
			
		||||
  $ret .= 'warnings = (' . (empty($result['warnings']) ? '' : '"' . implode('", "', $result['warnings']) . '"') . ')<br/>';
 | 
			
		||||
  $ret .= 'ignored = (' . (empty($result['ignored']) ? '' : implode(', ', $result['ignored'])) . ')<br/>';
 | 
			
		||||
  $ret .= nl2br(check_plain(print_r($result['performance'], TRUE)));
 | 
			
		||||
  return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_entity_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_entity_info() {
 | 
			
		||||
  return array(
 | 
			
		||||
    'search_api_test' => array(
 | 
			
		||||
      'label' => 'Search API test entity',
 | 
			
		||||
      'base table' => 'search_api_test',
 | 
			
		||||
      'uri callback' => 'search_api_test_uri',
 | 
			
		||||
      'entity keys' => array(
 | 
			
		||||
        'id' => 'id',
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_entity_property_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_entity_property_info() {
 | 
			
		||||
  $info['search_api_test']['properties'] = array(
 | 
			
		||||
    'id' => array(
 | 
			
		||||
      'label' => 'ID',
 | 
			
		||||
      'type' => 'integer',
 | 
			
		||||
      'description' => 'The primary identifier for a server.',
 | 
			
		||||
    ),
 | 
			
		||||
    'title' => array(
 | 
			
		||||
      'label' => 'Title',
 | 
			
		||||
      'type' => 'text',
 | 
			
		||||
      'description' => 'The title of the item.',
 | 
			
		||||
      'required' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
    'body' => array(
 | 
			
		||||
      'label' => 'Body',
 | 
			
		||||
      'type' => 'text',
 | 
			
		||||
      'description' => 'A text belonging to the item.',
 | 
			
		||||
      'sanitize' => 'filter_xss',
 | 
			
		||||
      'required' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
    'type' => array(
 | 
			
		||||
      'label' => 'Type',
 | 
			
		||||
      'type' => 'text',
 | 
			
		||||
      'description' => 'A string identifying the type of item.',
 | 
			
		||||
      'required' => TRUE,
 | 
			
		||||
    ),
 | 
			
		||||
    'parent' => array(
 | 
			
		||||
      'label' => 'Parent',
 | 
			
		||||
      'type' => 'search_api_test',
 | 
			
		||||
      'description' => "The item's parent.",
 | 
			
		||||
      'getter callback' => 'search_api_test_parent',
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return $info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * URI callback for test entity.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_uri($entity) {
 | 
			
		||||
  return array(
 | 
			
		||||
    'path' => 'search_api_test/' . $entity->id,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parent callback.
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_parent($entity) {
 | 
			
		||||
  return search_api_test_load($entity->id - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_search_api_service_info().
 | 
			
		||||
 */
 | 
			
		||||
function search_api_test_search_api_service_info() {
 | 
			
		||||
  $services['search_api_test_service'] = array(
 | 
			
		||||
    'name' => 'search_api_test_service',
 | 
			
		||||
    'description' => 'search_api_test_service description',
 | 
			
		||||
    'class' => 'SearchApiTestService',
 | 
			
		||||
  );
 | 
			
		||||
  return $services;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test service class.
 | 
			
		||||
 */
 | 
			
		||||
class SearchApiTestService extends SearchApiAbstractService {
 | 
			
		||||
 | 
			
		||||
  public function configurationForm(array $form, array &$form_state) {
 | 
			
		||||
    $form = array(
 | 
			
		||||
      'test' => array(
 | 
			
		||||
        '#type' => 'textfield',
 | 
			
		||||
        '#title' => 'Test option',
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (!empty($this->options)) {
 | 
			
		||||
      $form['test']['#default_value'] = $this->options['test'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function indexItems(SearchApiIndex $index, array $items) {
 | 
			
		||||
    // Refuse to index items with IDs that are multiples of 8 unless the
 | 
			
		||||
    // "search_api_test_index_all" variable is set.
 | 
			
		||||
    if (variable_get('search_api_test_index_all', FALSE)) {
 | 
			
		||||
      return $this->index($index, array_keys($items));
 | 
			
		||||
    }
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    foreach ($items as $id => $item) {
 | 
			
		||||
      if ($id % 8) {
 | 
			
		||||
        $ret[] = $id;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->index($index, $ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function index(SearchApiIndex $index, array $ids) {
 | 
			
		||||
    $this->options += array('indexes' => array());
 | 
			
		||||
    $this->options['indexes'] += array($index->machine_name => array());
 | 
			
		||||
    $this->options['indexes'][$index->machine_name] += drupal_map_assoc($ids);
 | 
			
		||||
    sort($this->options['indexes'][$index->machine_name]);
 | 
			
		||||
    $this->server->save();
 | 
			
		||||
    return $ids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Override so deleteItems() isn't called which would otherwise lead to the
 | 
			
		||||
   * server being updated and, eventually, to a notice because there is no
 | 
			
		||||
   * server to be updated anymore.
 | 
			
		||||
   */
 | 
			
		||||
  public function preDelete() {}
 | 
			
		||||
 | 
			
		||||
  public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
 | 
			
		||||
    if ($ids == 'all') {
 | 
			
		||||
      if ($index) {
 | 
			
		||||
        $this->options['indexes'][$index->machine_name] = array();
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $this->options['indexes'] = array();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      foreach ($ids as $id) {
 | 
			
		||||
        unset($this->options['indexes'][$index->machine_name][$id]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->server->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function search(SearchApiQueryInterface $query) {
 | 
			
		||||
    $options = $query->getOptions();
 | 
			
		||||
    $ret = array();
 | 
			
		||||
    $index_id = $query->getIndex()->machine_name;
 | 
			
		||||
    if (empty($this->options['indexes'][$index_id])) {
 | 
			
		||||
      return array(
 | 
			
		||||
        'result count' => 0,
 | 
			
		||||
        'results' => array(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $items = $this->options['indexes'][$index_id];
 | 
			
		||||
    $min = isset($options['offset']) ? $options['offset'] : 0;
 | 
			
		||||
    $max = $min + (isset($options['limit']) ? $options['limit'] : count($items));
 | 
			
		||||
    $i = 0;
 | 
			
		||||
    $ret['result count'] = count($items);
 | 
			
		||||
    $ret['results'] = array();
 | 
			
		||||
    foreach ($items as $id) {
 | 
			
		||||
      ++$i;
 | 
			
		||||
      if ($i > $max) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if ($i > $min) {
 | 
			
		||||
        $ret['results'][$id] = array(
 | 
			
		||||
          'id' => $id,
 | 
			
		||||
          'score' => 1,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function fieldsUpdated(SearchApiIndex $index) {
 | 
			
		||||
    return db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField() > 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user