Module:Item link: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
(Better handling for maps, which nearly always return multiple results when queried by name. Now uses the one from the most recent map series, regardless of whether it's drop enabled. This prevents item links from breaking when maps are removed from the Atlas.)
No edit summary
Line 67: Line 67:
      
      
     if m_util.table.has_one_value(tpl_args, cfg.selectors, nil) and tpl_args.skip_query == nil then
     if m_util.table.has_one_value(tpl_args, cfg.selectors, nil) and tpl_args.skip_query == nil then
         local tables = {'items', 'maps', 'map_series'}
         local tables = {'items'}
         local fields = {
         local fields = {
             'items._pageName',
             'items._pageName',
Line 80: Line 80:
         }
         }
         local query = {
         local query = {
            join = 'items._pageID = maps._pageID, maps.series = map_series.name',
             groupBy = 'items._pageID',
             groupBy = 'items._pageID',
             orderBy = 'map_series.ordinal DESC, items.drop_enabled DESC',
             orderBy = 'items.drop_enabled DESC',
         }
         }
         local search_param
         local search_param
Line 95: Line 94:
             tables[#tables+1] = '_pageData'
             tables[#tables+1] = '_pageData'
             fields[#fields+1] = '_pageData._pageNameOrRedirect'
             fields[#fields+1] = '_pageData._pageNameOrRedirect'
            query.join = 'items._pageName = _pageData._pageNameOrRedirect'
             query.where = string.format(
             query.where = string.format(
                 '_pageData._pageName="%s"',
                 '_pageData._pageName="%s"',
                 tpl_args.page
                 tpl_args.page
             )
             )
            query.join = query.join .. ', items._pageName = _pageData._pageNameOrRedirect'
             search_param = 'page'
             search_param = 'page'
         elseif tpl_args.item_name_exact then
         elseif tpl_args.item_name_exact then
Line 109: Line 108:
             search_param = 'item_name_exact'
             search_param = 'item_name_exact'
         else
         else
            tables[#tables+1] = 'maps'
            tables[#tables+1] = 'map_series'
             -- Explicitly join name_list child table instead of using HOLDS
             -- Explicitly join name_list child table instead of using HOLDS
             tables[#tables+1] = 'items__name_list'
             tables[#tables+1] = 'items__name_list'
            query.join = 'items._ID = items__name_list._rowID, items._pageID = maps._pageID, maps.series = map_series.name'
             query.where = string.format(
             query.where = string.format(
                 'items__name_list._value="%s" AND items._pageNamespace IN (%s)',
                 'items__name_list._value = "%s" AND items._pageNamespace IN (%s)',
                 m_cargo.addslashes(tpl_args.item_name),
                 m_cargo.addslashes(tpl_args.item_name),
                 m_item_util.get_item_namespaces{format = 'list'}
                 m_item_util.get_item_namespaces{format = 'list'}
             )
             )
             query.join = query.join .. ', items._ID = items__name_list._rowID'
             query.orderBy = 'map_series.ordinal DESC, items.drop_enabled DESC'
             search_param = 'item_name'
             search_param = 'item_name'
         end
         end
       
         result = m_cargo.query(tables, fields, query)
         result = m_cargo.query(
            tables,
            fields,
            query
        )
       
         local err
         local err
         if #result == 0 then
         if #result == 0 then
Line 166: Line 162:
             end
             end
         end
         end
       
         if err ~= nil then
         if err ~= nil then
             return err .. m_util.misc.add_category({i18n.categories.broken_item_links})
             return err .. m_util.misc.add_category({i18n.categories.broken_item_links})
         end
         end
       
         result = result[1] -- Use the first result
         result = result[1] -- Use the first result
     else
     else

Revision as of 20:45, 15 January 2022

Module documentation[view] [edit] [history] [purge]


This module is used on 45000+ pages.

To avoid major disruption and server load, do not make unnecessary edits to this module. Test changes to this module first using its /sandbox and /testcases subpages or your user space. All of the changes can then be applied to this module in a single edit.

Consider discussing changes on the talk page or on Discord before implementing them.

Lua logo

This module depends on the following other modules:

This module implements {{item link}} and facilitates the creation of item links.

-------------------------------------------------------------------------------
-- 
--                             Module:Item link
-- 
-- This module implements Template:Item link.
-- 
-- This is separate from the main item module for small speed ups. Those speed 
-- ups are only sigificant if the module is called a lot of times (100+), in 
-- tests this amounted to only a ~10% difference in page load times at best. It 
-- should be noted those tests are difficult because of the large variance in 
-- page load times.
-------------------------------------------------------------------------------

local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_item_util = require('Module:Item util')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item link')

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item link/config/sandbox') or mw.loadData('Module:Item link/config')

local i18n = cfg.i18n

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

--
-- Template:Item link
--
function p.item_link (frame)
    --[[
    Creates a link to the item and displays the item info box on hover 
    on the link.
    
    Examples
    --------
    = p.item_link{'Multistrike'}
    = p.item_link{'Multistrike Support'}
    
    ]]
    
    -- Get arguments:
    local tpl_args = getArgs(frame, {
        parentFirst = true,
        removeBlanks = false,
    })
    frame = m_util.misc.get_frame(frame)
    
    tpl_args.item_name = tpl_args.item_name or tpl_args[1]
    tpl_args.name = tpl_args.name or tpl_args[2]
    
    if m_util.table.has_all_value(tpl_args, cfg.selectors) and tpl_args.skip_query == nil then
        error(i18n.errors.invalid_args)
    end
    
    tpl_args.large = m_util.cast.boolean(tpl_args.large)
    
    local img
    local result
    
    if m_util.table.has_one_value(tpl_args, cfg.selectors, nil) and tpl_args.skip_query == nil then
        local tables = {'items'}
        local fields = {
            'items._pageName',
            'items.name',
            'items.inventory_icon',
            'items.html',
            'items.alternate_art_inventory_icons',
            'items.size_x',
            'items.size_y',
            'items.drop_enabled',
            'items.class_id',
        }
        local query = {
            groupBy = 'items._pageID',
            orderBy = 'items.drop_enabled DESC',
        }
        local search_param
        if tpl_args.metadata_id then
            query.where = string.format(
                'items.metadata_id="%s"',
                tpl_args.metadata_id
            )
            search_param = 'metadata_id'
        elseif tpl_args.page then
            -- Join with _pageData in order to check for page redirect
            tables[#tables+1] = '_pageData'
            fields[#fields+1] = '_pageData._pageNameOrRedirect'
            query.join = 'items._pageName = _pageData._pageNameOrRedirect'
            query.where = string.format(
                '_pageData._pageName="%s"',
                tpl_args.page
            )
            search_param = 'page'
        elseif tpl_args.item_name_exact then
            query.where = string.format(
                'items.name = "%s" AND items._pageNamespace IN (%s)',
                m_cargo.addslashes(tpl_args.item_name_exact),
                m_item_util.get_item_namespaces{format = 'list'}
            )
            search_param = 'item_name_exact'
        else
            tables[#tables+1] = 'maps'
            tables[#tables+1] = 'map_series'
            -- Explicitly join name_list child table instead of using HOLDS
            tables[#tables+1] = 'items__name_list'
            query.join = 'items._ID = items__name_list._rowID, items._pageID = maps._pageID, maps.series = map_series.name'
            query.where = string.format(
                'items__name_list._value = "%s" AND items._pageNamespace IN (%s)',
                m_cargo.addslashes(tpl_args.item_name),
                m_item_util.get_item_namespaces{format = 'list'}
            )
            query.orderBy = 'map_series.ordinal DESC, items.drop_enabled DESC'
            search_param = 'item_name'
        end
        result = m_cargo.query(tables, fields, query)
        local err
        if #result == 0 then
            -- No results found
            err = m_util.misc.raise_error_or_return{
                raise_required=true,
                args=tpl_args, 
                msg=string.format(
                    i18n.errors.no_results,
                    search_param,
                    tpl_args[search_param]
                )
            }
        elseif #result > 1 then
            -- More than one result found
            -- 
            -- If results are all maps, use the one from the most recent map
            -- series, regardless of whether it's drop enabled. Otherwise, 
            -- if only one of the results is drop enabled then use that one.
            local map_count = 0
            local drop_enabled_count = 0
            for i, v in ipairs(result) do
                if v['items.class_id'] == 'Map' then
                    map_count = map_count + 1
                end
                if m_util.cast.boolean(v['items.drop_enabled']) then
                    drop_enabled_count = drop_enabled_count + 1
                end
            end
            if (map_count == 0 or map_count ~= #result) and drop_enabled_count ~= 1 then
                err = m_util.misc.raise_error_or_return{
                    raise_required=true, 
                    args=tpl_args, 
                    msg=string.format(
                        i18n.errors.too_many_results,
                        search_param,
                        tpl_args[search_param]
                    )
                }
            end
        end
        if err ~= nil then
            return err .. m_util.misc.add_category({i18n.categories.broken_item_links})
        end
        result = result[1] -- Use the first result
    else
        result = {
            ['items._pageName'] = tpl_args.page or tpl_args.name
        }
    end
    
    for k, prop in pairs(cfg.parameters) do
        if tpl_args[k] ~= nil then
            result[prop] = tpl_args[k]
        end
    end
    
    if tpl_args.image ~= nil then
        if result['items.alternate_art_inventory_icons'] == nil then
            return m_util.misc.raise_error_or_return{
                raise_required=true, 
                args=tpl_args, 
                msg=string.format(
                    i18n.errors.alt_art_undefined,
                    result['items._pageName']
                ) .. m_util.misc.add_category({i18n.categories.broken_item_links})
            }
        end
        
        result['items.alternate_art_inventory_icons'] = m_util.string.split(
            result['items.alternate_art_inventory_icons'], 
            ',%s*'
        )
        
        local index = tonumber(tpl_args.image)
        if index ~= nil then
            img = result['items.alternate_art_inventory_icons'][index]
        else
            -- offset 1 is needed
            local suffix = string.len(' inventory icon.png') + 1 
            -- add an extra offset by 1 to account for the space 
            local prefix = string.len(string.sub(result['items.inventory_icon'], 1, -suffix)) + 2
            
            for _, filename in ipairs(result['items.alternate_art_inventory_icons']) do
                if string.sub(filename, prefix, -suffix) == tpl_args.image then
                    img = filename
                    break
                end
            end
        end
        
        if img == nil then
            return m_util.misc.raise_error_or_return{
                raise_required=true, 
                args=tpl_args, 
                msg=string.format(
                    i18n.errors.alt_art_invalid_index,
                    tpl_args.image, result['items._pageName']
                ) .. m_util.misc.add_category({i18n.categories.broken_item_links})
            }
        end
    elseif result['items.inventory_icon'] ~= nil then
        img = result['items.inventory_icon']
    end
    
    --
    -- output
    --
    
    -- Maps have their main page on the item name now, link there instead.
    -- Hopefully there are no maps with identical names besides the series.
    local linked_page
    if result['items.class_id'] == 'Map' and tpl_args.page == nil then 
        linked_page = tpl_args.link or tpl_args.item_name
    else
        linked_page = tpl_args.link or result['items._pageName']
    end
    
    local container = mw.html.create('span')
    container:addClass('c-item-hoverbox')

    if tpl_args.large then
        container:addClass('c-item-hoverbox--large')
    end
    
    local activator = mw.html.create('span')
    activator:addClass('c-item-hoverbox__activator')

    if img and not tpl_args.large then
        activator:wikitext(string.format('[[%s|16x16px|link=|alt=]]', img))
    end
    
    if #result['items.name'] > 0 then
        activator:wikitext(string.format(
            '[[%s|%s]]', 
            linked_page, 
            result['items.name'] or result['items._pageName']
            )
        )
    end
    
    local display = mw.html.create('span')
    display:attr('class', 'c-item-hoverbox__display')

    if result['items.html'] ~= nil then
        display:wikitext(result['items.html'])
            
        if img then
            display:wikitext(string.format('[[%s|link=|alt=]]', img))
        end
    end

    if img and tpl_args.large then
        local width = tonumber(result['items.size_x']) or tonumber(tpl_args.width)
        local height = tonumber(result['items.size_y']) or tonumber(tpl_args.height)
        if width and height then
            img = string.format(
                '[[%s|%sx%spx|link=%s|alt=]]', 
                img, 
                width*cfg.image_size, 
                height*cfg.image_size, 
                linked_page
            )
        elseif width then
            img = string.format(
                '[[%s|%spx|link=%s|alt=]]', 
                img, 
                width*cfg.image_size, 
                linked_page
            )
        elseif height then
            img = string.format(
                '[[%s|x%spx|link=%s|alt=]]', 
                img, 
                height*cfg.image_size, 
                linked_page
            )
        else
            img = string.format(
                '[[%s|link=%s|alt=]]', 
                img, 
                linked_page
            )
        end
        activator:wikitext(img)
    end

    container
        :node(activator)
        :node(display)
        :done()
        
    return tostring(container)
end

return p