WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1
) WHERE name LIKE 'credit.processor.stripe%' AND update_perm IS NULL;
+-- 2.10.1+ starts here
+
+CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
+DECLARE
+
+ owning_lib TEXT;
+ circ_lib TEXT;
+ call_number TEXT;
+ copy_number TEXT;
+ status TEXT;
+ location TEXT;
+ circulate TEXT;
+ deposit TEXT;
+ deposit_amount TEXT;
+ ref TEXT;
+ holdable TEXT;
+ price TEXT;
+ barcode TEXT;
+ circ_modifier TEXT;
+ circ_as_type TEXT;
+ alert_message TEXT;
+ opac_visible TEXT;
+ pub_note TEXT;
+ priv_note TEXT;
+ internal_id TEXT;
+ stat_cat_data TEXT;
+ parts_data TEXT;
+
+ attr_def RECORD;
+ tmp_attr_set RECORD;
+ attr_set vandelay.import_item%ROWTYPE;
+
+ xpaths TEXT[];
+ tmp_str TEXT;
+
+BEGIN
+
+ SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
+
+ IF FOUND THEN
+
+ attr_set.definition := attr_def.id;
+
+ -- Build the combined XPath
+
+ owning_lib :=
+ CASE
+ WHEN attr_def.owning_lib IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '*[@code="' || attr_def.owning_lib || '"]'
+ ELSE '*' || attr_def.owning_lib
+ END;
+
+ circ_lib :=
+ CASE
+ WHEN attr_def.circ_lib IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '*[@code="' || attr_def.circ_lib || '"]'
+ ELSE '*' || attr_def.circ_lib
+ END;
+
+ call_number :=
+ CASE
+ WHEN attr_def.call_number IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.call_number ) = 1 THEN '*[@code="' || attr_def.call_number || '"]'
+ ELSE '*' || attr_def.call_number
+ END;
+
+ copy_number :=
+ CASE
+ WHEN attr_def.copy_number IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.copy_number ) = 1 THEN '*[@code="' || attr_def.copy_number || '"]'
+ ELSE '*' || attr_def.copy_number
+ END;
+
+ status :=
+ CASE
+ WHEN attr_def.status IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.status ) = 1 THEN '*[@code="' || attr_def.status || '"]'
+ ELSE '*' || attr_def.status
+ END;
+
+ location :=
+ CASE
+ WHEN attr_def.location IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.location ) = 1 THEN '*[@code="' || attr_def.location || '"]'
+ ELSE '*' || attr_def.location
+ END;
+
+ circulate :=
+ CASE
+ WHEN attr_def.circulate IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.circulate ) = 1 THEN '*[@code="' || attr_def.circulate || '"]'
+ ELSE '*' || attr_def.circulate
+ END;
+
+ deposit :=
+ CASE
+ WHEN attr_def.deposit IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.deposit ) = 1 THEN '*[@code="' || attr_def.deposit || '"]'
+ ELSE '*' || attr_def.deposit
+ END;
+
+ deposit_amount :=
+ CASE
+ WHEN attr_def.deposit_amount IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '*[@code="' || attr_def.deposit_amount || '"]'
+ ELSE '*' || attr_def.deposit_amount
+ END;
+
+ ref :=
+ CASE
+ WHEN attr_def.ref IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.ref ) = 1 THEN '*[@code="' || attr_def.ref || '"]'
+ ELSE '*' || attr_def.ref
+ END;
+
+ holdable :=
+ CASE
+ WHEN attr_def.holdable IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.holdable ) = 1 THEN '*[@code="' || attr_def.holdable || '"]'
+ ELSE '*' || attr_def.holdable
+ END;
+
+ price :=
+ CASE
+ WHEN attr_def.price IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.price ) = 1 THEN '*[@code="' || attr_def.price || '"]'
+ ELSE '*' || attr_def.price
+ END;
+
+ barcode :=
+ CASE
+ WHEN attr_def.barcode IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.barcode ) = 1 THEN '*[@code="' || attr_def.barcode || '"]'
+ ELSE '*' || attr_def.barcode
+ END;
+
+ circ_modifier :=
+ CASE
+ WHEN attr_def.circ_modifier IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '*[@code="' || attr_def.circ_modifier || '"]'
+ ELSE '*' || attr_def.circ_modifier
+ END;
+
+ circ_as_type :=
+ CASE
+ WHEN attr_def.circ_as_type IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '*[@code="' || attr_def.circ_as_type || '"]'
+ ELSE '*' || attr_def.circ_as_type
+ END;
+
+ alert_message :=
+ CASE
+ WHEN attr_def.alert_message IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.alert_message ) = 1 THEN '*[@code="' || attr_def.alert_message || '"]'
+ ELSE '*' || attr_def.alert_message
+ END;
+
+ opac_visible :=
+ CASE
+ WHEN attr_def.opac_visible IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '*[@code="' || attr_def.opac_visible || '"]'
+ ELSE '*' || attr_def.opac_visible
+ END;
+
+ pub_note :=
+ CASE
+ WHEN attr_def.pub_note IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.pub_note ) = 1 THEN '*[@code="' || attr_def.pub_note || '"]'
+ ELSE '*' || attr_def.pub_note
+ END;
+ priv_note :=
+ CASE
+ WHEN attr_def.priv_note IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.priv_note ) = 1 THEN '*[@code="' || attr_def.priv_note || '"]'
+ ELSE '*' || attr_def.priv_note
+ END;
+
+ internal_id :=
+ CASE
+ WHEN attr_def.internal_id IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.internal_id ) = 1 THEN '*[@code="' || attr_def.internal_id || '"]'
+ ELSE '*' || attr_def.internal_id
+ END;
+
+ stat_cat_data :=
+ CASE
+ WHEN attr_def.stat_cat_data IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.stat_cat_data ) = 1 THEN '*[@code="' || attr_def.stat_cat_data || '"]'
+ ELSE '*' || attr_def.stat_cat_data
+ END;
+
+ parts_data :=
+ CASE
+ WHEN attr_def.parts_data IS NULL THEN 'null()'
+ WHEN LENGTH( attr_def.parts_data ) = 1 THEN '*[@code="' || attr_def.parts_data || '"]'
+ ELSE '*' || attr_def.parts_data
+ END;
+
+
+
+ xpaths := ARRAY[owning_lib, circ_lib, call_number, copy_number, status, location, circulate,
+ deposit, deposit_amount, ref, holdable, price, barcode, circ_modifier, circ_as_type,
+ alert_message, pub_note, priv_note, internal_id, stat_cat_data, parts_data, opac_visible];
+
+ FOR tmp_attr_set IN
+ SELECT *
+ FROM oils_xpath_tag_to_table( (SELECT marc FROM vandelay.queued_bib_record WHERE id = import_id), attr_def.tag, xpaths)
+ AS t( ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
+ dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
+ circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, internal_id TEXT,
+ stat_cat_data TEXT, parts_data TEXT, opac_vis TEXT )
+ LOOP
+
+ attr_set.import_error := NULL;
+ attr_set.error_detail := NULL;
+ attr_set.deposit_amount := NULL;
+ attr_set.copy_number := NULL;
+ attr_set.price := NULL;
+ attr_set.circ_modifier := NULL;
+ attr_set.location := NULL;
+ attr_set.barcode := NULL;
+ attr_set.call_number := NULL;
+
+ IF tmp_attr_set.pr != '' THEN
+ tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
+ IF tmp_str = '' THEN
+ attr_set.import_error := 'import.item.invalid.price';
+ attr_set.error_detail := tmp_attr_set.pr; -- original value
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ attr_set.price := tmp_str::NUMERIC(8,2);
+ END IF;
+
+ IF tmp_attr_set.dep_amount != '' THEN
+ tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
+ IF tmp_str = '' THEN
+ attr_set.import_error := 'import.item.invalid.deposit_amount';
+ attr_set.error_detail := tmp_attr_set.dep_amount;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ attr_set.deposit_amount := tmp_str::NUMERIC(8,2);
+ END IF;
+
+ IF tmp_attr_set.cnum != '' THEN
+ tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
+ IF tmp_str = '' THEN
+ attr_set.import_error := 'import.item.invalid.copy_number';
+ attr_set.error_detail := tmp_attr_set.cnum;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ attr_set.copy_number := tmp_str::INT;
+ END IF;
+
+ IF tmp_attr_set.ol != '' THEN
+ SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.owning_lib';
+ attr_set.error_detail := tmp_attr_set.ol;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ IF tmp_attr_set.clib != '' THEN
+ SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.circ_lib';
+ attr_set.error_detail := tmp_attr_set.clib;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ IF tmp_attr_set.cs != '' THEN
+ SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.status';
+ attr_set.error_detail := tmp_attr_set.cs;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ IF COALESCE(tmp_attr_set.circ_mod, '') = '' THEN
+
+ -- no circ mod defined, see if we should apply a default
+ SELECT INTO attr_set.circ_modifier TRIM(BOTH '"' FROM value)
+ FROM actor.org_unit_ancestor_setting(
+ 'vandelay.item.circ_modifier.default',
+ attr_set.owning_lib
+ );
+
+ -- make sure the value from the org setting is still valid
+ PERFORM 1 FROM config.circ_modifier WHERE code = attr_set.circ_modifier;
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.circ_modifier';
+ attr_set.error_detail := tmp_attr_set.circ_mod;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+
+ ELSE
+
+ SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.circ_modifier';
+ attr_set.error_detail := tmp_attr_set.circ_mod;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ IF tmp_attr_set.circ_as != '' THEN
+ SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.circ_as_type';
+ attr_set.error_detail := tmp_attr_set.circ_as;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ IF COALESCE(tmp_attr_set.cl, '') = '' THEN
+ -- no location specified, see if we should apply a default
+
+ SELECT INTO attr_set.location TRIM(BOTH '"' FROM value)
+ FROM actor.org_unit_ancestor_setting(
+ 'vandelay.item.copy_location.default',
+ attr_set.owning_lib
+ );
+
+ -- make sure the value from the org setting is still valid
+ PERFORM 1 FROM asset.copy_location
+ WHERE id = attr_set.location AND NOT deleted;
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.location';
+ attr_set.error_detail := tmp_attr_set.cs;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ ELSE
+
+ -- search up the org unit tree for a matching copy location
+ WITH RECURSIVE anscestor_depth AS (
+ SELECT ou.id,
+ out.depth AS depth,
+ ou.parent_ou
+ FROM actor.org_unit ou
+ JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
+ WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
+ UNION ALL
+ SELECT ou.id,
+ out.depth,
+ ou.parent_ou
+ FROM actor.org_unit ou
+ JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
+ JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
+ ) SELECT cpl.id INTO attr_set.location
+ FROM anscestor_depth a
+ JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
+ WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
+ AND NOT cpl.deleted
+ ORDER BY a.depth DESC
+ LIMIT 1;
+
+ IF NOT FOUND THEN
+ attr_set.import_error := 'import.item.invalid.location';
+ attr_set.error_detail := tmp_attr_set.cs;
+ RETURN NEXT attr_set; CONTINUE;
+ END IF;
+ END IF;
+
+ attr_set.circulate :=
+ LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
+ OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
+
+ attr_set.deposit :=
+ LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
+ OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
+
+ attr_set.holdable :=
+ LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
+ OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
+
+ attr_set.opac_visible :=
+ LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
+ OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
+
+ attr_set.ref :=
+ LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
+ OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
+
+ attr_set.call_number := tmp_attr_set.cn; -- TEXT
+ attr_set.barcode := tmp_attr_set.bc; -- TEXT,
+ attr_set.alert_message := tmp_attr_set.amessage; -- TEXT,
+ attr_set.pub_note := tmp_attr_set.note; -- TEXT,
+ attr_set.priv_note := tmp_attr_set.pnote; -- TEXT,
+ attr_set.alert_message := tmp_attr_set.amessage; -- TEXT,
+ attr_set.internal_id := tmp_attr_set.internal_id::BIGINT;
+ attr_set.stat_cat_data := tmp_attr_set.stat_cat_data; -- TEXT,
+ attr_set.parts_data := tmp_attr_set.parts_data; -- TEXT,
+
+ RETURN NEXT attr_set;
+
+ END LOOP;
+
+ END IF;
+
+ RETURN;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+-- note: it is not necessary to explicitly drop the previous VARIADIC
+-- version of this stored procedure; create or replace function...
+-- suffices.
+CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting_batch( org_id INT, setting_names TEXT[] ) RETURNS SETOF actor.org_unit_setting AS $$
+DECLARE
+ setting RECORD;
+ setting_name TEXT;
+ cur_org INT;
+BEGIN
+ FOREACH setting_name IN ARRAY setting_names
+ LOOP
+ cur_org := org_id;
+ LOOP
+ SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
+ IF FOUND THEN
+ RETURN NEXT setting;
+ EXIT;
+ END IF;
+ SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
+ EXIT WHEN cur_org IS NULL;
+ END LOOP;
+ END LOOP;
+ RETURN;
+END;
+$$ LANGUAGE plpgsql STABLE;
+
+COMMENT ON FUNCTION actor.org_unit_ancestor_setting_batch( INT, TEXT[] ) IS $$
+For each setting name passed, search "up" the org_unit tree until
+we find the first occurrence of an org_unit_setting with the given name.
+$$;
+
+
+CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT, bib_is_deleted BOOL DEFAULT FALSE, retain_deleted BOOL DEFAULT FALSE ) RETURNS BIGINT AS $func$
+DECLARE
+ new_mapping BOOL := TRUE;
+ source_count INT;
+ old_mr BIGINT;
+ tmp_mr metabib.metarecord%ROWTYPE;
+ deleted_mrs BIGINT[];
+BEGIN
+
+ -- We need to make sure we're not a deleted master record of an MR
+ IF bib_is_deleted THEN
+ FOR old_mr IN SELECT id FROM metabib.metarecord WHERE master_record = bib_id LOOP
+
+ IF NOT retain_deleted THEN -- Go away for any MR that we're master of, unless retained
+ DELETE FROM metabib.metarecord_source_map WHERE source = bib_id;
+ END IF;
+
+ -- Now, are there any more sources on this MR?
+ SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = old_mr;
+
+ IF source_count = 0 AND NOT retain_deleted THEN -- No other records
+ deleted_mrs := ARRAY_APPEND(deleted_mrs, old_mr); -- Just in case...
+ DELETE FROM metabib.metarecord WHERE id = old_mr;
+
+ ELSE -- indeed there are. Update it with a null cache and recalcualated master record
+ UPDATE metabib.metarecord
+ SET mods = NULL,
+ master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
+ WHERE id = old_mr;
+ END IF;
+ END LOOP;
+
+ ELSE -- insert or update
+
+ FOR tmp_mr IN SELECT m.* FROM metabib.metarecord m JOIN metabib.metarecord_source_map s ON (s.metarecord = m.id) WHERE s.source = bib_id LOOP
+
+ -- Find the first fingerprint-matching
+ IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN
+ old_mr := tmp_mr.id;
+ new_mapping := FALSE;
+
+ ELSE -- Our fingerprint changed ... maybe remove the old MR
+ DELETE FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id AND source = bib_id; -- remove the old source mapping
+ SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
+ IF source_count = 0 THEN -- No other records
+ deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
+ DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
+ END IF;
+ END IF;
+
+ END LOOP;
+
+ -- we found no suitable, preexisting MR based on old source maps
+ IF old_mr IS NULL THEN
+ SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
+
+ IF old_mr IS NULL THEN -- nope, create one and grab its id
+ INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
+ SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
+
+ ELSE -- indeed there is. update it with a null cache and recalcualated master record
+ UPDATE metabib.metarecord
+ SET mods = NULL,
+ master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
+ WHERE id = old_mr;
+ END IF;
+
+ ELSE -- there was one we already attached to, update its mods cache and master_record
+ UPDATE metabib.metarecord
+ SET mods = NULL,
+ master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC LIMIT 1)
+ WHERE id = old_mr;
+ END IF;
+
+ IF new_mapping THEN
+ INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
+ END IF;
+
+ END IF;
+
+ IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
+ UPDATE action.hold_request SET target = old_mr WHERE target IN ( SELECT unnest(deleted_mrs) ) AND hold_type = 'M'; -- if we had to delete any MRs above, make sure their holds are moved
+ END IF;
+
+ RETURN old_mr;
+
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION evergreen.oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
+ use strict;
+
+ use XML::LibXSLT;
+ use XML::LibXML;
+
+ my $doc = shift;
+ my $xslt = shift;
+
+ # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
+ # methods of parsing XML documents and stylesheets, in the hopes of broader
+ # compatibility with distributions
+ my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
+
+ # Cache the XML parser, if we do not already have one
+ $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
+ unless ($_SHARED{'_xslt_process'}{parsers}{xml});
+
+ my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
+
+ # Cache the XSLT processor, if we do not already have one
+ $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
+ unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
+
+ my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
+ $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
+
+ $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
+ unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
+
+ return $stylesheet->output_as_chars(
+ $stylesheet->transform(
+ $parser->parse_string($doc)
+ )
+ );
+
+$func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
+
+
+DO $INSERT$
+BEGIN
+ IF evergreen.insert_on_deploy() THEN
+
+INSERT INTO config.i18n_locale (code,marc_code,name,description)
+ SELECT 'es-ES', 'spa', oils_i18n_gettext('es-ES', 'Spanish', 'i18n_l', 'name'),
+ oils_i18n_gettext('es-ES', 'Spanish', 'i18n_l', 'description')
+ WHERE NOT EXISTS (SELECT 1 FROM config.i18n_locale WHERE code = 'es-ES');
+
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.1', '2.10.0');
+PERFORM evergreen.upgrade_deps_block_check('0975', '2.10.1');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.2', '2.10.1');
+PERFORM evergreen.upgrade_deps_block_check('0976', '2.10.2');
+--PERFORM evergreen.upgrade_deps_block_check('0977', '2.10.2');
+PERFORM evergreen.upgrade_deps_block_check('0978', '2.10.2');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.3', '2.10.2');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.4', '2.10.3');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.5', '2.10.4');
+--PERFORM evergreen.upgrade_deps_block_check('0981', '2.10.4');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.6', '2.10.4');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.7', '2.10.6');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.8', '2.10.7');
+PERFORM evergreen.upgrade_deps_block_check('1001', '2.10.8');
+PERFORM evergreen.upgrade_deps_block_check('1002', '2.10.8');
+PERFORM evergreen.upgrade_deps_block_check('1003', '2.10.8');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.9', '2.10.8');
+PERFORM evergreen.upgrade_deps_block_check('1018', '2.10.9');
+PERFORM evergreen.upgrade_deps_block_check('1030', '2.10.9');
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('2.10.12', '2.10.9');
+ END IF;
+END $INSERT$;
+
COMMIT;
-- ROLLBACK; -- TESTING ONLY