128 Commits

Author SHA1 Message Date
Sarjuuk
10ef33f709 Spells/SpellClick
* evaluate npc_spellclick_data and display on Spell and NPC Detail Pages
 * also categorize these spells as NPC-spells
 * closes #438
2025-11-21 22:47:43 +01:00
Sarjuuk
ae1b6c59b1 Quests/Requisites
* fixed case where an exclusiveGroup of 0 wasn't considered
 * closes #456
2025-11-21 21:09:09 +01:00
Sarjuuk
b764200c2a SmartAI/Conditions
* embed Conditions into SmartAI table so we can evaluate CONDITION_SOURCE_TYPE_SMART_EVENT (22)
 * make SmartAI table display flexible
2025-11-20 23:50:23 +01:00
Sarjuuk
c0454917ac Localization/Typo
* fix acc creation prompt for locale ES
2025-11-20 23:50:23 +01:00
Sarjuuk
b3e215cc40 Site/layout
* increase max width of layout from 1340px to 1640px
2025-11-19 20:30:57 +01:00
Sarjuuk
d4694cd2db User/Reputation
* fix storing negative reputation values
2025-11-19 20:30:57 +01:00
Sarjuuk
a5051c9bf5 Loot/Modes
* work against more correctly assigning instance mode to entities and loot
    - added manually collected data for difficulty versions of gameobjects, just boss chests for now.
      update setup/source to default object source to base difficulty version if able
    - update spelldifficulty table to contain the (likely) mapmode it will be used in
  * refactored class loot
    - implement loot mode indicators on listview for creature and gameobject loot
    - show 'drops' listview tab on instance zone page
    - fixes against tribute chest systems (toc / ulduar)
    - fix icc gunship battle chest ownership
2025-11-19 20:22:33 +01:00
Sarjuuk
be3701df91 Params/Fixup
* FILTER_SANITIZE_URL is absurdly strict and will not tolerate umlauts or spaces
   replaced with printable chars regex
2025-11-19 17:33:16 +01:00
Sarjuuk
9b905883df Listview/Conditions
* make column width flex
2025-11-19 16:59:57 +01:00
Sarjuuk
9db3e766da QuestDetailPage/ShowOnMap
* increase strictness for sources of required items shown on mapper from 1% to 5%
2025-11-19 16:59:57 +01:00
Sarjuuk
7f29c1d4b7 CurrencyDetailPage/Tabs
* add currency column to display gains
2025-11-19 16:59:57 +01:00
Sarjuuk
57665aaa9e ItemDetailPage/Misc
* fix "vendor in" mapper
 * add "fished in" mapper
 * move 'see-also' and 'same-model-as' tabs to the back of the tabs list
2025-11-19 16:59:57 +01:00
Sarjuuk
4cb544182d Setup/Spawns
* evaluate waypoint paths linked via creature_template_addon
2025-11-15 22:17:09 +01:00
Sarjuuk
a2b87da285 Localization/CN
* fix excess whitespaces betweeen number and unit
2025-11-15 22:17:03 +01:00
Sarjuuk
03bab92cb8 DateTime/Fixup
* fix displaying expected '0 seconds' instead of 'n/a' or '1 ms'
2025-11-15 22:16:58 +01:00
Sarjuuk
103287f91b Setup/Pets
* move custom data from pet script to db
2025-11-15 22:16:53 +01:00
Sarjuuk
82f36fd342 Setup/Source
* generally flag items of quality artifact as unavailable
 * 04f3aa7a82 caused some items transformed by spell to be 'available'
2025-11-15 22:16:48 +01:00
Sarjuuk
f5654ae21f DateTime
* recreate date functions from javascript in new class DateTime
 * move date and time functions from Util to new class
 * fixes various cooldown messages for account recovery
2025-11-14 19:16:12 +01:00
Sarjuuk
1fe3690244 Cache/Fixup
* fix cache collision on list pages caused by improper encoding of category
 * fix cache key not encoding category values of int: 0
 * version bump to flush caches
2025-11-13 21:29:35 +01:00
Sarjuuk
45417122c2 UserPage/Fixup
* only show related heading if we have tabs to display
2025-11-11 20:35:42 +01:00
Sarjuuk
7cf5dded98 Signatures
* add non-functional endpoint stubs with info
2025-11-11 20:17:39 +01:00
Sarjuuk
31f51276b2 Profiler/Fixup
* fix exception when manually querying for unsynced guild/arena-team
   that shares it's name with another guild/team (e.g. ìíîi is the same to SQL)
   and the target guild/team not being the first result
2025-11-10 20:31:09 +01:00
Sarjuuk
643c3c2a83 Comments/Goto
* fix comment links in reputation history on user page
2025-11-10 18:45:39 +01:00
Sarjuuk
a135dfce90 Profiler/Completions
* add keyed col `exalted` to reputation completions table to speed up lookups
2025-11-09 19:05:33 +01:00
Sarjuuk
0d42d2a2c4 UserPage/Optimization
* split up fetching of custom profiles and characters to make use of existing keys
2025-11-09 16:22:26 +01:00
Sarjuuk
fa89a5ad1e User/Fixup
* fix fetching user characters, borked in 474b5b5aec062b61e8d707c91739b50ad77e81ef
 * take #2
2025-11-09 16:22:00 +01:00
Sarjuuk
6eb5a67add Profiler/Optimization
* move searchable flags to their own db cols to speed up lookups
 * don't cast profile name to LOWER in SQL when displaying tooltips.
2025-11-09 16:20:52 +01:00
Sarjuuk
8a169eb400 Setup/Fixup
* catch error if url from self test is unreachable
2025-11-09 16:16:28 +01:00
Sarjuuk
cf4e8a527c User/Fixup
* fix fetching user characters, borked in 474b5b5aec062b61e8d707c91739b50ad77e81ef
2025-11-08 18:07:26 +01:00
Sarjuuk
48564ab8b5 Misc/Fixup
* fix building num ranges, added in 8212811970
2025-11-07 21:19:43 +01:00
Sarjuuk
edc297f97a NPCs/Fixup
* fix parent npcs name for locales CN and ES
2025-11-07 20:47:39 +01:00
Sarjuuk
5d02a20719 SQL/Misc
* add keys to spells table to speed up related spells queries
2025-11-07 20:34:49 +01:00
Sarjuuk
f44de66de7 User/Profiles
* speed up load of user profiles
2025-11-07 20:34:47 +01:00
Sarjuuk
16c5b73cd3 User/Misc
* don't run querys if not strictly required (e.g. query for chars from ajax context)
 * prepare user globals and favorites so errors can be handled and don't destroy the template
   this also allows for profiling of the affected queries
 * add keys to items table to speed up querying for recipes in general and user completions in particular
2025-11-05 15:39:28 +01:00
Sarjuuk
9020e36db6 SmartAI/Misc
* make errors more verbose if SAI tries to set unexpected flags
 * do not escape strings. By now thats handled by Frontend/Markup
2025-11-04 19:48:26 +01:00
Sarjuuk
597898450d WorldEvent/Misc
* fix excess colons in tooltip
 * fix advancing date window while event is still active
2025-11-04 19:48:19 +01:00
Sarjuuk
6a94888686 Filter/Fixup
* try to prune deselected criteria/weight selectors from filter input
2025-11-04 00:04:24 +01:00
Sarjuuk
e3d6f7b3a7 Profiler/Completions
* show completion info for claimed characters in infobox on
      appropriate db pages
2025-11-03 20:50:54 +01:00
Sarjuuk
37380ff515 Frontend/InfoboxMarkup
* you can now pass attributes to the [li] element
2025-11-03 18:47:06 +01:00
Sarjuuk
8212811970 Misc/Cleanup
* create function for num range .. creation
2025-11-01 21:01:03 +01:00
Sarjuuk
1e9e406ff0 TextResponse/Fixup
* make class non-abstract so we can generate a 403/404 message on base
2025-10-31 16:44:37 +01:00
Sarjuuk
3984bd0ae2 Spells/Misc
* limit chance/ppm precision on spell procs chances
 * do not apply a spells EffectXBonusMultiplier for physical spells
2025-10-29 15:51:23 +01:00
Sarjuuk
441ad38543 Filter/Locales
* fix embedding and triggering fi_toggle on filtrable listviews for locale zhCN
2025-10-28 19:58:47 +01:00
Sarjuuk
88cc76feae Localization/Filters
* add missing spell effects & aura names
 * don't return null for unused effects/auras (triggers an error)
2025-10-28 19:58:44 +01:00
Sarjuuk
96c777191d Spells/Scaling
* move scaling data to the appropriate spell effects (like WH)
2025-10-27 21:19:40 +01:00
Sarjuuk
40b2830cad Spells/Scaling
* hopefully fix a lot of nonsensical spell scaling infos
 * note: an aweful lot of physical spells are hardcoded or have spell scripts and won't display any info
2025-10-27 19:44:00 +01:00
Sarjuuk
9741774683 Spells/Reagents
* fix reagents listing and spell tooltips for nonexistent reagents
2025-10-27 17:02:30 +01:00
Sarjuuk
e8bc37f82f Filter/NPC
* fix react filter
 * remove excess colons
2025-10-27 01:47:27 +01:00
Sarjuuk
7cbe1f6007 Reports/Fixup
* also include source url when checking target context
 * cleanup source url to be usable as key
2025-10-26 19:19:50 +01:00
Sarjuuk
3a25c2390f Listviews/AddIns
* AddIns must be output directly before the listview it is used by
2025-10-26 17:32:18 +01:00
Sarjuuk
cf2ace805b Spawns/Fixup
* fix maps for single-floor dungeons borked in 33cd290dc3
2025-10-24 18:29:41 +02:00
Sarjuuk
2f8e035783 Search/Fixup
* fix redirecting to result on exact hit
2025-10-24 18:28:25 +02:00
Sarjuuk
1a55b30766 UserPage/Fixup
* move description inside text container
 * add missing 'related' heading
2025-10-23 20:49:58 +02:00
Sarjuuk
6d7f9c0f00 Quest/Fixup
* don't try to create an objective for empty SourceItem
 * fixed size of source spell icon
2025-10-23 16:26:59 +02:00
Sarjuuk
862b3dff73 IconElement/Fixup
* do not change type of num / qty params ('+1' is not numeric)
2025-10-23 00:48:04 +02:00
Sarjuuk
b1f22f7e68 Spells/ExtraLoot
* display extra loot from skill_extra_item_template like perfect gems
 * for specializations, display affected spells in Bonus Loot tab
 * cleanup subject->id to typeId
 * closes #286
2025-10-23 00:10:52 +02:00
Sarjuuk
1d922c1147 Locks
* implemented display of LOCK_TYPE_SPELL (3 cases)
 * show "unlocks" tab on spell detail page
 * closes #288
2025-10-22 22:18:27 +02:00
Sarjuuk
f9ace6a671 Spell/Sources
* always display quest source from RewardSpellCast
 * fix inherited quest sources via learn spells
 * closes #353
2025-10-22 18:57:33 +02:00
Sarjuuk
6ea1457c4f Guides/Fixup
* make preview area of class: text so styles are applied as expected
 * fix evaluation of Markup h2/h3 attribute toc=false, so headings are excluded from TOC as expected
2025-10-22 18:06:38 +02:00
Sarjuuk
f522c960d9 Modelviewer/Fixup
* fix buttons of lightbox
2025-10-22 17:08:36 +02:00
Sarjuuk
1365cdb261 Spell/Effects
* show spells affected by SPELL_AURA_IGNORE_COMBAT_RESULT
2025-10-22 01:57:58 +02:00
Sarjuuk
9b591e7a3a Items/Tooltips
* fix itemId to scientific notation conversion, when itemId was joined
   by an enchantmentId (1234e56 => 1.234e53), breaking tooltip display.
2025-10-21 23:35:42 +02:00
Sarjuuk
6da71afc68 Filter/Fixup
* allow unicode chars when checking GET param
2025-10-21 20:53:27 +02:00
Sarjuuk
033a9181ae Profiler/Localization
* TCs guild and arena_team tables as encoded as utf8mb4_general_ci,
   which is not accent-aware. So we have to get all results and filter
   for the correct one in php.
 * fixes an issue where direcly accessing a guild/arena-team whith a name
   simiar to an already known guild/team would lookup the wrong subject
   on the server and then fail to create a local stub with already existing key.
   (Shâdów vs Shadow)
2025-10-21 17:57:00 +02:00
Sarjuuk
1dcc9363da Profiler/Filter
* fix filter params being propagated between different profiler types
   (e.g. arena team size filter being appended to guilds menu)
2025-10-21 15:35:28 +02:00
Sarjuuk
51b6e29316 Profiler/Queue
* send ready status for characters/guilds/arena-teams whose resync
   cooldown hasn't expired yet
2025-10-20 20:38:15 +02:00
Sarjuuk
14c159c164 Setup/Factions
* fix switched base rep field indizes, causing Profiler to miscalculate
   character standing
 * replace hardcoded sql table prefixes
2025-10-20 19:23:52 +02:00
Sarjuuk
33cd290dc3 Setup/Spawns
* fix coords for cases with coords in both WorldMapArea.dbc and
   DungeonMap.dbc without using WorldMapArea.dbc as base floor
2025-10-20 19:23:02 +02:00
Sarjuuk
2e029f3d96 Profiler/Talents
* fix building talent string for hunter pets.
   the alternate spells (e.g. Dash & Swoop) must both be included
 * align talent order in build scripts talenticons, talentcalc with
   Profiler talent string builder
 * fix Shamans gaining 5% Parry by talent Spirit Weapons
 * cleanup
2025-10-20 16:25:24 +02:00
Sarjuuk
6a32c770cd Profiler/Pets
* catch error case where a player owns a pet that is no longer tameable/has no pet family
2025-10-19 22:46:08 +02:00
Sarjuuk
4d421d2bbb Filter/Errors
* handle stat weights quirk, analogous to the criteria quirk
2025-10-18 16:46:25 +02:00
Sarjuuk
176cf137fb Filter/Fixup
* criteria parameters can be placeholder/null
2025-10-17 14:33:52 +02:00
Sarjuuk
830edb8265 PageTemplate/Fixup
* use get_object_vars() instead of property_exists() to test if we can
   load a variable from provided context. The former only returns
   accessible vars while the latter returns true for all properties.
2025-10-16 02:14:47 +02:00
Sarjuuk
7d8b524478 Listview/Cost
* do not append 0 achievementpoints to cost builder. It will be displayed
2025-10-15 21:53:00 +02:00
Sarjuuk
95918c0410 SoundDetailPage/Fixup
* fix exception when assigning WorldState conditions to the zones tab
2025-10-15 01:06:57 +02:00
Sarjuuk
a275955ee3 Admin/Config
* handle Ajax errors
2025-10-15 00:26:05 +02:00
Sarjuuk
37beaa2db5 Filter/Errors
* move checks to __construct so they can be run on $_POST data
   and don't create malformed filter urls
 * if we received malformed $_GET params, build new params and reload
 * do not store error state in cache
 * cleanup
2025-10-15 00:05:55 +02:00
Sarjuuk
c0097f3987 Mapper/Objectives
* fix display of item objectives by making LocString JsonSerializable
2025-10-14 16:17:59 +02:00
Sarjuuk
92c58cc5d1 Localization/UIEscapes
* fixed expanding |2 placeholder in general and when the referenced word itself was a placeholder ($N => <nom>)
 * fixed expanding |3 placeholder for caseIds > 9
2025-10-13 20:37:39 +02:00
Sarjuuk
04f3aa7a82 Setup/Source
* respect disabled Quests and Spells when flagging Items as unavailable
 * reuse data from loot_link to set difficuly bits and zoneId
   for loot container GOs
2025-10-12 22:32:52 +02:00
Sarjuuk
65d490a8ae Enchantments/Stats
* entirely forgo ?_item_stats table when calculating enchantment stats
2025-10-12 22:24:09 +02:00
Sarjuuk
816eacaf73 Summary/Fixup
* allow signed integers (random enchantments) in summary definition
2025-10-12 22:24:03 +02:00
Sarjuuk
034eca1f58 Items/RandEnchants
* fix amount calculation for scaling enchantments
 * cache RandomPropPoints lookups
2025-10-12 22:22:56 +02:00
Sarjuuk
a33abb84fe Setup/Account
* don't overwrite existing account in case of email conflict
2025-10-12 17:48:06 +02:00
Sarjuuk
fb7b22db36 Account/Passwords
* use buildin php functions to handle passwords
 * increase cost of BCRYPT
 * make use of the SensitiveParameter attribute
2025-10-12 17:48:06 +02:00
Sarjuuk
dd838fa994 Misc/Doc
* add several ItemMods unusd by client but still found in item_template as comment
2025-10-12 00:57:09 +02:00
Sarjuuk
d32074fdcd ZoneDetailPage/Tabs
* only offer 'filter result' prompt on tabs for zones that are filtrable
2025-10-12 00:15:59 +02:00
Sarjuuk
494061de82 Cache/Fixup
* correct miscData offset for tooltips introduced in 3edac3c77a
 * fix generating cache key for item upgrade searches
2025-10-12 00:15:52 +02:00
Sarjuuk
9b0aa5c885 Achievements/Fixup
* fix fetching achievements from child catgs if selected catg is empty
2025-10-12 00:15:28 +02:00
Sarjuuk
40e98081c9 LatestComments/RSS
* fix url format for replies
2025-10-12 00:15:15 +02:00
Sarjuuk
77f2a0c21d Profiler/Resync
* fix logging ids on resync failure
2025-10-10 22:33:31 +02:00
Sarjuuk
465e019eaa FactionDetailPage/Tabs
* only offer 'filter result' prompt on tabs for factions that are filtrable
2025-10-10 22:33:25 +02:00
Sarjuuk
63053757c9 Guides/Fixup
* fix showing wrong guide version to staff
 * fix sticky icon offset
2025-10-10 20:53:56 +02:00
Sarjuuk
a96f6c4cdf PageTemplate/Fixup
* really fix merging jsGlobals from comments/etc. into existing PageTemplate
2025-10-10 20:49:56 +02:00
Sarjuuk
b832fc172c Items/Gearscore
* fix warning in GS calculation
2025-10-10 20:49:43 +02:00
Sarjuuk
196f60f176 Filter/Zones
* add missing Ruby Sanctum to zones dropdown
 * sort zones alphabetically
2025-10-10 20:49:32 +02:00
Sarjuuk
204d4b8ae2 Profiler/Sync
* fix SQL FK error when creating guild or arenateam stub.
 * Urlized name field is non-optional
2025-10-10 20:49:14 +02:00
Sarjuuk
5d2fd00358 Profiler/Save
* fixed inventory definitions not allowing for negative ids (random enchantments)
 * added handling invalid inventory definitions
2025-10-10 20:49:07 +02:00
Sarjuuk
1dcdf9623b Endpoints/User
* do not display user page for internal system user
2025-10-08 20:02:48 +02:00
Sarjuuk
215ad39cc6 NPC/Reputation
* try to fix reputation spillover
 * fix extra colon on reputation gains
2025-10-08 19:48:36 +02:00
Sarjuuk
a9ed897ea6 Filter/Fixup
* fix evaluating imbalanced criteria
2025-10-08 19:48:29 +02:00
Sarjuuk
3edac3c77a Endpoints/Cache
* fix cache id collision when category == dbTypeId for a given dbType
 * increment version number to invalidate existing caches
 * maps endpoint doesn't need caching. It is entirely static content.
2025-10-08 19:48:21 +02:00
Sarjuuk
95ee9d2c25 NPCs/Fixup
* fix exception when displaying NPC with elemental resistances in base
   version but not difficulty modes
2025-10-07 15:51:07 +02:00
Sarjuuk
d79742d599 Profiler/Fixup
* dont use unsynced profile stubs in attained % calculation
2025-10-06 23:23:40 +02:00
Sarjuuk
e300086cc8 IconElement/Fixup
* a DOMElements text value must be escaped manually
   (e.g. Foror &amp; Tigule)
2025-10-06 23:23:30 +02:00
Sarjuuk
a7e9ac2cf2 Misc/Fixup
* HTTP_USER_AGENT is not guaranteed to be set
2025-10-06 17:16:41 +02:00
Sarjuuk
05f5b0ed34 Response/Params
* so we can't directly use BackedEnum::tryFrom as validator, because if
   the Enum is of <int> and the string is not what php considers numeric,
   we get a straight TypeError Exception instead of null for failing the tryFrom.
2025-10-06 17:06:49 +02:00
Sarjuuk
e37620c01b Search/Fixup
* fix pruning empty tokens from search
2025-10-06 17:06:43 +02:00
Sarjuuk
c40bd3851b Profiler/Fixup
* fix scoring perm enchantments
2025-10-06 17:06:34 +02:00
Sarjuuk
452615a92d Filters/Misc
* be a bit more lenient on level inputs
 * fix displaying array of requirements on error
2025-10-06 17:06:25 +02:00
Sarjuuk
704894c1e3 Spells/Fixup
* skillLines can be empty for unused glyphs etc.
2025-10-06 17:06:14 +02:00
Sarjuuk
045c16c241 PageTemplate/Profiler
* don't skip running parent::generate for incomplete profiles
2025-10-06 15:00:47 +02:00
Sarjuuk
7b429811a9 Defines/Races
* add unplayable races to ChrRace enum so RaceDetailPage can display them.
 * don't show empty icons for unplayable races
2025-10-05 20:20:40 +02:00
Sarjuuk
aa7c0186fc Profiler/Cleanup
* gracefully handle DB errors when fetching realms instead of crashing
2025-10-05 20:19:48 +02:00
Sarjuuk
7b752143a0 Creature/Quotes
* don't add superfluous creature name placeholder to emotes
2025-10-05 18:55:39 +02:00
Sarjuuk
baf4ba5b98 Codestyle/Cleanup 2025-10-05 00:28:27 +02:00
Sarjuuk
eb95b03e31 SmartAI/Update
* implement events added in 3bb4f56773
2025-10-04 20:02:34 +02:00
Sarjuuk
4fe35d9e3c PageTemplate/Fixup
* fix merging jsGlobals from comments/etc. into existing PageTemplate
2025-10-04 17:13:45 +02:00
Sarjuuk
5355989015 Logging/Misc
* don't log passwords to DB (and neither check_passwords)
2025-10-04 17:13:34 +02:00
Sarjuuk
9fc84cdf9e Comments/Fixup
* fix false error when voting on relies
2025-10-04 15:39:44 +02:00
Sarjuuk
a6108be400 Spells/Parsing
* bandaid fix parsing deeply nested formulas in non-interactive mode
   (should rethink how/when formulas get flagged as un-evalable)
2025-10-04 01:10:38 +02:00
Sarjuuk
ff690770b5 Misc/Fixup
* type error when declaring listview
2025-10-04 00:15:38 +02:00
Sarjuuk
6263ccd92a SkillDetailPage/Tabs
* add tab for spells modifying skill value
2025-10-03 18:32:27 +02:00
Sarjuuk
60eb816002 Quickfacts
* where applicable:
   - show typeId
   - show english name if currently localized
   - show percentage attained by synced profiles
 * fixed some typos
2025-10-03 17:49:50 +02:00
Sarjuuk
bc112b2b16 Template/Fixup
* fix directly adding dataloader to PageTemplate
2025-10-03 17:49:49 +02:00
Sarjuuk
6d86f880f4 Analytics/Cookies
* don't ask users to consent on GA tracking if we don't use GA tracking
2025-10-02 19:53:53 +02:00
Sarjuuk
bd1f139c2e CLI/Misc
* handle error case where setup is run automated and receives no input on STDIN
2025-10-02 16:16:49 +02:00
Yrito
36aa33ac26 Setup/CLI
* Allow starting setup at specific step
2025-10-02 14:35:13 +02:00
214 changed files with 5855 additions and 3114 deletions

View File

@@ -4,7 +4,7 @@ if (!defined('AOWOW_REVISION'))
die('illegal access');
function extAuth(string &$usernameOrEmail, string $password, int &$userId = 0, int &$userGroup = -1) : int
function extAuth(string &$usernameOrEmail, #[\SensitiveParameter] string $password, int &$userId = 0, int &$userGroup = -1) : int
{
/*
insert some auth mechanism here

View File

@@ -112,7 +112,7 @@ class AccountBaseResponse extends TemplateResponse
// Username
$this->curName = User::$username;
$this->renameCD = Util::formatTime(Cfg::get('ACC_RENAME_DECAY') * 1000);
$this->renameCD = DateTime::formatTimeElapsedFloat(Cfg::get('ACC_RENAME_DECAY') * 1000);
if ($user['renameCooldown'] > time())
{
$locCode = implode('_', str_split(Lang::getLocale()->json(), 2)); // ._.

View File

@@ -76,7 +76,7 @@ class AccountforgotpasswordResponse extends TemplateResponse
// on cooldown pretend we dont know the email address
if ($timeout && $timeout > time())
return Cfg::get('DEBUG') ? 'resend on cooldown: '.Util::formatTimeDiff($timeout).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
return Cfg::get('DEBUG') ? 'resend on cooldown: '.DateTime::formatTimeElapsed($timeout * 1000).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
// pretend recovery started
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE `email` = ?', $this->_post['email']))

View File

@@ -75,7 +75,7 @@ class AccountforgotusernameResponse extends TemplateResponse
// on cooldown pretend we dont know the email address
if ($timeout && $timeout > time())
return Cfg::get('DEBUG') ? 'resend on cooldown: '.Util::formatTimeDiff($timeout).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
return Cfg::get('DEBUG') ? 'resend on cooldown: '.DateTime::formatTimeElapsed($timeout * 1000).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
// pretend recovery started
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE `email` = ?', $this->_post['email']))

View File

@@ -73,7 +73,7 @@ class AccountResendResponse extends TemplateResponse
// on cooldown pretend we dont know the email address
if ($timeout && $timeout > time())
return Cfg::get('DEBUG') ? 'resend on cooldown: '.Util::formatTimeDiff($timeout).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
return Cfg::get('DEBUG') ? 'resend on cooldown: '.DateTime::formatTimeElapsed($timeout * 1000).' remaining' : Lang::account('inputbox', 'error', 'emailNotFound');
// check email and account status
if ($token = DB::Aowow()->selectCell('SELECT `token` FROM ?_account WHERE `email` = ? AND `status` = ?d', $this->_post['email'], ACC_STATUS_NEW))

View File

@@ -25,7 +25,7 @@ class AccountresetpasswordResponse extends TemplateResponse
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']],
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
'next' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/' ]]
);
protected array $expectedPOST = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']],

View File

@@ -28,7 +28,7 @@ class AccountSigninResponse extends TemplateResponse
);
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']],
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
'next' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/'] ]
);
private bool $success = false;
@@ -99,7 +99,7 @@ class AccountSigninResponse extends TemplateResponse
// AUTH_BANNED => Lang::account('accBanned'); // ToDo: should this return an error? the actual account functionality should be blocked elsewhere
AUTH_WRONGUSER => Lang::account('userNotFound'),
AUTH_WRONGPASS => Lang::account('wrongPass'),
AUTH_IPBANNED => Lang::account('inputbox', 'error', 'loginExceeded', [Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]),
AUTH_IPBANNED => Lang::account('inputbox', 'error', 'loginExceeded', [DateTime::formatTimeElapsedFloat(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]),
AUTH_INTERNAL_ERR => Lang::main('intError'),
default => Lang::main('intError')
};

View File

@@ -11,8 +11,8 @@ class AccountSignoutResponse extends TextResponse
use TrGetNext;
protected array $expectedGET = array(
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH],
'global' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet'] ]
'next' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/']],
'global' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet'] ]
);
public function __construct(string $pageParam)

View File

@@ -26,7 +26,7 @@ class AccountSignupResponse extends TemplateResponse
);
protected array $expectedGET = array(
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW]
'next' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/']]
);
private bool $success = false;
@@ -112,7 +112,7 @@ class AccountSignupResponse extends TemplateResponse
if (DB::Aowow()->selectRow('SELECT 1 FROM ?_account_bannedips WHERE `type` = ?d AND `ip` = ? AND `count` >= ?d AND `unbanDate` >= UNIX_TIMESTAMP()', IP_BAN_TYPE_REGISTRATION_ATTEMPT, User::$ip, Cfg::get('ACC_FAILED_AUTH_COUNT')))
{
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ? AND `type` = ?d', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip, IP_BAN_TYPE_REGISTRATION_ATTEMPT);
return Lang::account('inputbox', 'error', 'signupExceeded', [Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]);
return Lang::account('inputbox', 'error', 'signupExceeded', [DateTime::formatTimeElapsedFloat(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]);
}
// username / email taken

View File

@@ -53,7 +53,7 @@ class AccountUpdateemailResponse extends TextResponse
$status = DB::Aowow()->selectCell('SELECT `status` FROM ?_account WHERE `statusTimer` > UNIX_TIMESTAMP() AND `id` = ?d', User::$id);
if ($status != ACC_STATUS_NONE && $status != ACC_STATUS_CHANGE_EMAIL)
return Lang::account('isRecovering', [Util::formatTime(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
return Lang::account('inputbox', 'error', 'isRecovering', [DateTime::formatTimeElapsedFloat(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
$oldEmail = DB::Aowow()->selectCell('SELECT `email` FROM ?_account WHERE `id` = ?d', User::$id);
if ($this->_post['newemail'] == $oldEmail)

View File

@@ -55,7 +55,7 @@ class AccountUpdatepasswordResponse extends TextResponse
$userData = DB::Aowow()->selectRow('SELECT `status`, `passHash`, `statusTimer` FROM ?_account WHERE `id` = ?d', User::$id);
if ($userData['status'] != ACC_STATUS_NONE && $userData['status'] != ACC_STATUS_CHANGE_PASS && $userData['statusTimer'] > time())
return Lang::account('isRecovering', [Util::formatTime(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
return Lang::account('inputbox', 'error', 'isRecovering', [DateTime::formatTimeElapsedFloat(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
if (!User::verifyCrypt($this->_post['currentPassword'], $userData['passHash']))
return Lang::account('wrongPass');

View File

@@ -21,7 +21,7 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'achievement';
protected string $pageName = 'achievement';
@@ -107,15 +107,32 @@ class AchievementBaseResponse extends TemplateResponse implements ICache
default => Lang::game('si', SIDE_BOTH) // 0, 3
};
// id
$infobox[] = Lang::achievement('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && !($this->subject->getField('flags') & ACHIEVEMENT_FLAG_COUNTER))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_achievements WHERE `achievementId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', !($this->subject->getField('flags') & ACHIEVEMENT_FLAG_COUNTER));
/**********/

View File

@@ -11,7 +11,7 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ACHIEVEMENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'achievements';
protected string $pageName = 'achievements';
@@ -54,6 +54,12 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new AchievementListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -69,13 +75,9 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
if ($this->category)
$conditions[] = ['category', end($this->category)];
$this->filter->evalCriteria();
if ($fiCnd = $this->filter->getConditions())
$conditions[] = $fiCnd;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */
@@ -119,7 +121,7 @@ class AchievementsBaseResponse extends TemplateResponse implements ICache
$conditions = [];
if ($fiCnd)
$conditions[] = $fiCnd;
if ($catList = DB::Aowow()->SelectCol('SELECT `id` FROM ?_achievementcategory WHERE `parentCat` IN (?a) OR `parentCat2` IN (?a) ', end($this->category), end($this->category)))
if ($catList = DB::Aowow()->SelectCol('SELECT `id` FROM ?_achievementcategory WHERE `parentCat` IN (?a) OR `parentCat2` IN (?a) ', $this->category, $this->category))
$conditions[] = ['category', $catList];
$acvList = new AchievementList($conditions, ['calcTotal' => true]);

View File

@@ -10,7 +10,7 @@ class AreatriggerBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected int $requiredUserGroup = U_GROUP_STAFF;
protected string $template = 'detail-page-generic';
@@ -105,7 +105,7 @@ class AreatriggerBaseResponse extends TemplateResponse implements ICache
// tab: conditions
$cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_AREATRIGGER_CLIENT)->prepare();
$cnd->getBySource(Conditions::SRC_AREATRIGGER_CLIENT, entry: $this->typeId)->prepare();
if ($tab = $cnd->toListviewTab())
{
$this->extendGlobalData($cnd->getJsGlobals());

View File

@@ -11,7 +11,7 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::AREATRIGGER;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected int $requiredUserGroup = U_GROUP_STAFF;
protected string $template = 'areatriggers';
@@ -33,6 +33,12 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
parent::__construct($pageParam);
$this->filter = new AreaTriggerListFilter($this->_get['filter'] ?? '');
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -40,8 +46,6 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('areatriggers'));
$this->filter->evalCriteria();
$fiForm = $this->filter->values;
@@ -73,8 +77,6 @@ class AreatriggersBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
$tabData = [];
$trigger = new AreaTriggerList($conditions, ['calcTotal' => true]);
if (!$trigger->error)

View File

@@ -46,21 +46,24 @@ class ArenateamBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::ARENA_TEAM, $subject['realmGUID']);
return;
}
// 2) not yet synced but exists on realm (wont work if we get passed an urlized name, but there is nothing we can do about it)
else if ($subject = DB::Characters($this->realmId)->selectRow('SELECT at.`arenaTeamId` AS "realmGUID", at.`name`, at.`type` FROM arena_team at WHERE at.`name` = ?', Util::ucFirst($this->subjectName)))
$subjects = DB::Characters($this->realmId)->select('SELECT at.`arenaTeamId` AS "realmGUID", at.`name`, at.`type` FROM arena_team at WHERE at.`name` = ?', $this->subjectName);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) === Util::lower($this->subjectName)))
{
$subject = array_pop($subject);
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject['stub'] = 1;
$subject['nameUrl'] = Profiler::urlize($subject['name']);
// create entry from realm with basic info
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES (?a)', array_keys($subject), array_values($subject));

View File

@@ -53,6 +53,12 @@ class ArenateamsBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ArenaTeamListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}

View File

@@ -12,7 +12,7 @@ class ClassBaseResponse extends TemplateResponse implements ICache
private const TC_CLASS_IDS = [null, 8, 3, 1, 5, 4, 9, 6, 2, 7, null, 0]; // see TalentCalc.js
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'class';
@@ -103,6 +103,13 @@ class ClassBaseResponse extends TemplateResponse implements ICache
if ($specList)
$infobox[] = Lang::game('specs').'[ul][li]'.implode('[/li][li]', $specList).'[/li][/ul]';
// id
$infobox[] = Lang::chrClass('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class ClassesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CHR_CLASS;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'classes';

View File

@@ -47,7 +47,7 @@ class CommentDownvotereplyResponse extends TextResponse
User::canSupervote() ? -2 : -1
);
if (!$ok)
if (!is_int($ok))
{
trigger_error('CommentDownvotereplyResponse - write to db failed', E_USER_ERROR);
$this->generate404(User::isInGroup(U_GROUP_STAFF) ? 'write to db failed' : '');

View File

@@ -47,7 +47,7 @@ class CommentUpvotereplyResponse extends TextResponse
User::canSupervote() ? 2 : 1
);
if (!$ok)
if (!is_int($ok))
{
trigger_error('CommentUpvotereplyResponse - write to db failed', E_USER_ERROR);
$this->generate404(User::isInGroup(U_GROUP_STAFF) ? 'write to db failed' : '');

View File

@@ -106,7 +106,7 @@ class CompareBaseResponse extends TemplateResponse
protected static function checkCompareString(string $val) : string
{
$val = urldecode($val);
if (preg_match('/[^\d\.:;]/', $val))
if (preg_match('/[^-?\d\.:;]/', $val))
return '';
return $val;

View File

@@ -9,15 +9,15 @@ if (!defined('AOWOW_REVISION'))
class ContactusBaseResponse extends TextResponse
{
protected array $expectedPOST = array(
'mode' => ['filter' => FILTER_VALIDATE_INT ],
'reason' => ['filter' => FILTER_VALIDATE_INT ],
'ua' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']],
'appname' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']],
'page' => ['filter' => FILTER_SANITIZE_URL ],
'desc' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob']],
'id' => ['filter' => FILTER_VALIDATE_INT ],
'relatedurl' => ['filter' => FILTER_SANITIZE_URL ],
'email' => ['filter' => FILTER_SANITIZE_EMAIL ]
'mode' => ['filter' => FILTER_VALIDATE_INT ],
'reason' => ['filter' => FILTER_VALIDATE_INT ],
'ua' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ],
'appname' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ],
'page' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/']],
'desc' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ],
'id' => ['filter' => FILTER_VALIDATE_INT ],
'relatedurl' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[[:print:]]+$/']],
'email' => ['filter' => FILTER_SANITIZE_EMAIL ]
);
/* responses

View File

@@ -11,7 +11,7 @@ class CurrenciesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CURRENCY;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'currencies';

View File

@@ -10,7 +10,7 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'currency';
@@ -72,13 +72,20 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('cap'))
$infobox[] = Lang::currency('cap').Lang::nf($_);
// id
$infobox[] = Lang::currency('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -110,9 +117,9 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
if ($this->typeId != CURRENCY_HONOR_POINTS && $this->typeId != CURRENCY_ARENA_POINTS)
{
// tabs: this currency is contained in..
$lootTabs = new Loot();
$lootTabs = new LootByItem($_relItemId);
if ($lootTabs->getByItem($_relItemId))
if ($lootTabs->getByItem())
{
$this->extendGlobalData($lootTabs->jsGlobals);
@@ -121,6 +128,15 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
if ($template == 'npc' || $template == 'object')
$this->addDataLoader('zones');
if ($template != 'quest')
{
foreach ($tabData['data'] as &$row)
if (!empty($row['stack']))
$row['currency'] = [[$this->typeId, $row['stack'][0]]];
$tabData['extraCols'][] = '$Listview.extraCols.currency';
}
$this->lvTabs->addListviewTab(new Listview($tabData, $template));
}
}
@@ -182,7 +198,7 @@ class CurrencyBaseResponse extends TemplateResponse implements ICache
}
}
// tab: created by (spell) [for items its handled in Loot::getByContainer()]
// tab: created by (spell) [for items its handled in LootByItem]
if ($this->typeId == CURRENCY_HONOR_POINTS)
{
$createdBy = new SpellList(array(['effect1Id', SPELL_EFFECT_ADD_HONOR], ['effect2Id', SPELL_EFFECT_ADD_HONOR], ['effect3Id', SPELL_EFFECT_ADD_HONOR], 'OR'));

View File

@@ -9,7 +9,7 @@ if (!defined('AOWOW_REVISION'))
class DataBaseResponse extends TextResponse
{
protected array $expectedGET = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale' ]],
't' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine' ]],
'catg' => ['filter' => FILTER_VALIDATE_INT ],
'skill' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkSkill' ]],

View File

@@ -10,7 +10,7 @@ class EmoteBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'emote';
@@ -94,6 +94,9 @@ class EmoteBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::emote('id') . $this->typeId;
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -10,7 +10,7 @@ class EmotesBaseResponse extends TemplateResponse implements ICache
{
use TrListPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected int $type = Type::EMOTE;
protected string $template = 'list-page-generic';

View File

@@ -10,7 +10,7 @@ class EnchantmentBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'enchantment';
protected string $pageName = 'enchantment';
@@ -85,6 +85,13 @@ class EnchantmentBaseResponse extends TemplateResponse implements ICache
$infobox[] = $foo;
}
// id
$infobox[] = Lang::enchantment('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ENCHANTMENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'enchantments';
protected string $pageName = 'enchantments';
@@ -35,6 +35,12 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new EnchantmentListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -42,18 +48,13 @@ class EnchantmentsBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('enchantments'));
$this->filter->evalCriteria();
$conditions = [];
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -10,7 +10,7 @@ class EventBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'event';
@@ -88,9 +88,16 @@ class EventBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::npc('rank', 3).Lang::main('colon').'[npc='.$_.']';
}
// display internal id to staff
if (User::isInGroup(U_GROUP_STAFF))
$infobox[] = 'Event-Id'.Lang::main('colon').$this->typeId;
// id
$infobox[] = Lang::event('id') . $this->typeId;
// display holiday id to staff
if ($_holidayId && User::isInGroup(U_GROUP_STAFF))
$infobox[] = 'Holiday ID'.Lang::main('colon').$_holidayId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -320,7 +327,7 @@ class EventBaseResponse extends TemplateResponse implements ICache
// interval
if ($rec > 0)
$infobox[] = Lang::event('interval').Util::formatTime($rec * 1000);
$infobox[] = Lang::event('interval').DateTime::formatTimeElapsed($rec * 1000);
// in progress
if ($start < time() && $end > time())

View File

@@ -11,7 +11,7 @@ class EventsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::WORLDEVENT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'events';

View File

@@ -10,7 +10,7 @@ class FactionBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'faction';
@@ -96,8 +96,25 @@ class FactionBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('side'))
$infobox[] = Lang::main('side').'[span class=icon-'.($_ == SIDE_ALLIANCE ? 'alliance' : 'horde').']'.Lang::game('si', $_).'[/span]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
// id
$infobox[] = Lang::faction('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_reputation WHERE `exalted` = 1 AND `factionId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox) // unsure if this should be tracked (needs data dump in User::getCompletion())
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', 0);
/****************/
@@ -208,7 +225,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($items->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=17;crs='.$this->typeId.';crv=0');
if (!is_null(ItemListFilter::getCriteriaIndex(17, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=17;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile, 'itemStandingCol'));
}
@@ -242,7 +260,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($killCreatures->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=42;crs='.$this->typeId.';crv=0');
if (!is_null(CreatureListFilter::getCriteriaIndex(42, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=42;crs='.$this->typeId.';crv=0');
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile, 'npcRepCol'));
@@ -263,7 +282,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($members->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=3;crs='.$this->typeId.';crv=0');
if (!is_null(CreatureListFilter::getCriteriaIndex(3, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=3;crs='.$this->typeId.';crv=0');
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview($tabData, CreatureList::$brickFile));
@@ -301,7 +321,8 @@ class FactionBaseResponse extends TemplateResponse implements ICache
);
if ($quests->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['note'] = sprintf(Util::$filterResultString, '?quests&filter=cr=1;crs='.$this->typeId.';crv=0');
if (!is_null(QuestListFilter::getCriteriaIndex(1, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?quests&filter=cr=1;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview($tabData, QuestList::$brickFile, 'questRepCol'));
}

View File

@@ -11,7 +11,7 @@ class FactionsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::FACTION;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'factions';

View File

@@ -21,8 +21,9 @@ class GotocommentBaseResponse extends TextResponse
return;
}
// type <> 0 AND typeId <> 0 AND replyTo = 0 for comments
$comment = DB::Aowow()->selectRow('SELECT `id`, `type`, `typeId` FROM ?_comments WHERE `replyTo` = 0 AND `id` = ?d', $this->_get['id']);
// the reputation-history listview only creates go-to-comment links. So either upvoting replies does not grant reputation, or.... bug.?
$comment = DB::Aowow()->selectRow('SELECT IFNULL(c2.`id`, c1.`id`) AS "id", IFNULL(c2.`type`, c1.`type`) AS "type", IFNULL(c2.`typeId`, c1.`typeId`) AS "typeId" FROM ?_comments c1 LEFT JOIN ?_comments c2 ON c1.`replyTo` = c2.`id` WHERE c1.`id` = ?d', $this->_get['id']);
if (!$comment)
{
trigger_error('GotocommentBaseResponse - comment #'.$this->_get['id'].' not found', E_USER_ERROR);
@@ -36,6 +37,8 @@ class GotocommentBaseResponse extends TextResponse
}
$this->redirectTo = sprintf('?%s=%d#comments:id=%d', Type::getFileString($comment['type']), $comment['typeId'], $comment['id']);
if ($comment['id'] != $this->_get['id']) // i am reply
$this->redirectTo .= ':reply='.$this->_get['id'];
}
}

View File

@@ -33,7 +33,7 @@ class GuideChangelogResponse extends TemplateResponse
if (!$guide->canBeViewed() && !$guide->userCanView())
$this->forward('?guides='.$guide->getField('category'));
$this->h1 = lang::guide('clTitle', [$this->_get['id'], $guide->getField('title')]);
$this->h1 = Lang::guide('clTitle', [$this->_get['id'], $guide->getField('title')]);
if (!$this->h1)
$this->h1 = $guide->getField('name');
@@ -79,20 +79,21 @@ class GuideChangelogResponse extends TemplateResponse
$buff = '<ul>';
$inp = fn($rev) => User::isInGroup(U_GROUP_STAFF) && false ? ($rev !== null ? '<input name="a" value="'.$rev.'" type="radio"/><input name="b" value="'.$rev.'" type="radio"/><b>' : '<b style="margin-left:38px;">') : '';
$now = new DateTime();
$logEntries = DB::Aowow()->select('SELECT a.`username` AS `name`, gcl.`date`, gcl.`status`, gcl.`msg`, gcl.`rev` FROM ?_guides_changelog gcl JOIN ?_account a ON a.`id` = gcl.`userId` WHERE gcl.`id` = ?d ORDER BY gcl.`date` DESC', $this->_get['id']);
foreach ($logEntries as $log)
{
if ($log['status'] != GuideMgr::STATUS_NONE)
$buff .= '<li class="guide-changelog-status-change">'.$inp($log['rev']).'<b>'.Lang::guide('clStatusSet', [Lang::guide('status', $log['status'])]).'</b>'.Util::formatTimeDiff($log['date'])."</li>\n";
$buff .= '<li class="guide-changelog-status-change">'.$inp($log['rev']).'<b>'.Lang::guide('clStatusSet', [Lang::guide('status', $log['status'])]).'</b>'.$now->formatDate($log['date'], true)."</li>\n";
else if ($log['msg'])
$buff .= '<li>'.$inp($log['rev']).'<b>'.Util::formatTimeDiff($log['date']).Lang::main('colon').'</b>'.$log['msg'].' <i class="q0">'.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."</i></li>\n";
$buff .= '<li>'.$inp($log['rev']).'<b>'.$now->formatDate($log['date'], true).Lang::main('colon').'</b>'.$log['msg'].' <i class="q0">'.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."</i></li>\n";
else
$buff .= '<li class="guide-changelog-minor-edit">'.$inp($log['rev']).'<b>'.Util::formatTimeDiff($log['date']).Lang::main('colon').'</b><i>'.Lang::guide('clMinorEdit').'</i> <i class="q0">'.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."</i></li>\n";
$buff .= '<li class="guide-changelog-minor-edit">'.$inp($log['rev']).'<b>'.$now->formatDate($log['date'], true).Lang::main('colon').'</b><i>'.Lang::guide('clMinorEdit').'</i> <i class="q0">'.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."</i></li>\n";
}
// append creation
$buff .= '<li class="guide-changelog-created">'.$inp(0).'<b>'.Lang::guide('clCreated').'</b>'.Util::formatTimeDiff($guide->getField('date'))."</li>\n</ul>\n";
$buff .= '<li class="guide-changelog-created">'.$inp(0).'<b>'.Lang::guide('clCreated').'</b>'.$now->formatDate($guide->getField('date'), true)."</li>\n</ul>\n";
if (User::isInGroup(U_GROUP_STAFF) && false)
$buff .= '<input type="button" value="Compare" onclick="alert(\'NYI\');" style="margin-left: 40px;"/>';

View File

@@ -45,7 +45,7 @@ class GuideEditResponse extends TemplateResponse
'description' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkDescription'] ],
'changelog' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ],
'body' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom'] ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale'] ],
'category' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 9] ],
'specId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => -1, 'max_value' => 2, 'default' => -1]],
'classId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 11, 'default' => 0]]

View File

@@ -10,7 +10,7 @@ class GuideBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'guide';
@@ -68,12 +68,12 @@ class GuideBaseResponse extends TemplateResponse implements ICache
$this->contribute = CONTRIBUTE_NONE;
}
if ($this->articleUrl)
// owner or staff and manual rev passed
if ($this->subject->userCanView() && $this->_get['rev'])
$this->guideRevision = $this->_get['rev'];
// has publicly viewable version
else if ($this->subject->canBeViewed())
$this->guideRevision = $this->subject->getField('rev');
else if ($this->subject->userCanView())
$this->guideRevision = $this->_get['rev'] ?? $this->subject->getField('latest');
else
$this->subject->getField('rev');
$this->h1 = $this->subject->getField('name');
@@ -127,7 +127,7 @@ class GuideBaseResponse extends TemplateResponse implements ICache
$this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], __forceTabs: true);
// the article text itself is added by PageTemplate::addArticle()
// the article text itself is added by TemplateResponse::addArticle()
parent::generate();
$this->result->registerDisplayHook('infobox', [self::class, 'infoboxHook']);

View File

@@ -10,7 +10,7 @@ class GuidesBaseResponse extends TemplateResponse // implements ICache
{
use TrListPage/* , TrCache */;
// protected int $cacheType = CACHE_TYPE_PAGE; // really do? cache would need to be destroyed externally with each guide status update
// protected int $cacheType = CACHE_TYPE_LIST_PAGE; // really do? cache would need to be destroyed externally with each guide status update
protected int $type = Type::GUIDE;
protected string $template = 'list-page-generic';

View File

@@ -46,21 +46,24 @@ class GuildBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_guild WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_guild WHERE `realm` = ?d AND `nameUrl` = ?', $this->realmId, Profiler::urlize($this->subjectName)))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::GUILD, $subject['realmGUID']);
return;
}
// 2) not yet synced but exists on realm (wont work if we get passed an urlized name, but there is nothing we can do about it)
else if ($subject = DB::Characters($this->realmId)->selectRow('SELECT `guildid` AS "realmGUID", `name` FROM guild WHERE `name` = ?', Util::ucFirst($this->subjectName)))
$subjects = DB::Characters($this->realmId)->select('SELECT `guildid` AS "realmGUID", `name` FROM guild WHERE `name` = ?', $this->subjectName);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) === Util::lower($this->subjectName)))
{
$subject = array_pop($subject);
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject['stub'] = 1;
$subject['nameUrl'] = Profiler::urlize($subject['name']);
// create entry from realm with basic info
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES (?a)', array_keys($subject), array_values($subject));

View File

@@ -53,6 +53,12 @@ class GuildsBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new GuildListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}

View File

@@ -10,7 +10,7 @@ class IconBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'icon';
protected string $pageName = 'icon';

View File

@@ -11,7 +11,7 @@ class IconsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ICON;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'icons';
protected string $pageName = 'icons';
@@ -32,6 +32,12 @@ class IconsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new IconListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -43,13 +49,9 @@ class IconsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -10,7 +10,7 @@ class ItemBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'item';
protected string $pageName = 'item';
@@ -103,10 +103,13 @@ class ItemBaseResponse extends TemplateResponse implements ICache
SIDE_BOTH => Lang::game('si', SIDE_BOTH)
};
// id
$infobox[] = Lang::item('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
@@ -145,9 +148,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::item('tool').'[url=?items&filter=cr=91;crs='.$tId.';crv=0]'.Util::localizedString($tName, 'name').'[/url]';
// extendedCost
if (!empty($this->subject->getExtendedCost([], $_reqRating)[$this->subject->id]))
if (!empty($this->subject->getExtendedCost([], $_reqRating)[$this->typeId]))
{
$vendors = $this->subject->getExtendedCost()[$this->subject->id];
$vendors = $this->subject->getExtendedCost()[$this->typeId];
$stack = $this->subject->getField('buyCount');
$divisor = $stack;
$each = '';
@@ -316,8 +319,15 @@ class ItemBaseResponse extends TemplateResponse implements ICache
if ($_bagFamily & 0x0100)
$infobox[] = Lang::item('atKeyring');
// completion row added by InfoboxMarkup
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
$hasCompletion = !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW) && ($_class == ITEM_CLASS_RECIPE || ($_class == ITEM_CLASS_MISC && in_array($_subClass, [2, 5, -7])));
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
/****************/
@@ -433,9 +443,9 @@ class ItemBaseResponse extends TemplateResponse implements ICache
}
// tabs: this item is contained in..
$lootTabs = new Loot();
$lootTabs = new LootByItem($this->typeId);
$createdBy = [];
if ($lootTabs->getByItem($this->typeId))
if ($lootTabs->getByItem())
{
$this->extendGlobalData($lootTabs->jsGlobals);
@@ -444,23 +454,44 @@ class ItemBaseResponse extends TemplateResponse implements ICache
if (!$tabData['data'])
continue;
if ($idx == 16)
if ($idx == LootByItem::SPELL_CREATED)
$createdBy = array_column($tabData['data'], 'id');
if ($idx == 1)
if ($idx == LootByItem::ITEM_DISENCHANTED)
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=163;crs='.$this->typeId.';crv=0');
if ($idx == 4 && $this->subject->getSources($s, $sm) && $s[0] == SRC_DROP && isset($sm[0]['dd']))
{
switch ($sm[0]['dd'])
if ($idx == LootByItem::NPC_DROPPED && $this->subject->getSources($s, $sm) && $s[0] == SRC_DROP && isset($sm[0]['dd']))
$tabData['note'] = match($sm[0]['dd'])
{
case -1: $tabData['note'] = '$LANG.lvnote_itemdropsinnormalonly'; break;
case -2: $tabData['note'] = '$LANG.lvnote_itemdropsinheroiconly'; break;
case -3: $tabData['note'] = '$LANG.lvnote_itemdropsinnormalheroic'; break;
case 1: $tabData['note'] = '$LANG.lvnote_itemdropsinnormal10only'; break;
case 2: $tabData['note'] = '$LANG.lvnote_itemdropsinnormal25only'; break;
case 3: $tabData['note'] = '$LANG.lvnote_itemdropsinheroic10only'; break;
case 4: $tabData['note'] = '$LANG.lvnote_itemdropsinheroic25only'; break;
-1 => '$LANG.lvnote_itemdropsinnormalonly',
-2 => '$LANG.lvnote_itemdropsinheroiconly',
-3 => '$LANG.lvnote_itemdropsinnormalheroic',
1 => '$LANG.lvnote_itemdropsinnormal10only',
2 => '$LANG.lvnote_itemdropsinnormal25only',
3 => '$LANG.lvnote_itemdropsinheroic10only',
4 => '$LANG.lvnote_itemdropsinheroic25only',
default => null
};
if ($idx == LootByItem::OBJECT_FISHED && !$this->map)
{
$nodeIds = array_map(fn($x) => $x['id'], $tabData['data']);
$fishedIn = new GameObjectList(array(['id', $nodeIds]));
if (!$fishedIn->error)
{
// show mapper for fishing locations
if ($nodeSpawns = $fishedIn->getSpawns(SPAWNINFO_FULL, true, true, true, true))
{
$this->map = array(
['parent' => 'mapper-generic'], // Mapper
$nodeSpawns, // mapperData
null, // ShowOnMap
[Lang::item('fishedIn')], // foundIn
Lang::item('fishingLoc') // title
);
foreach ($nodeSpawns as $areaId => $_)
$this->map[3][$areaId] = ZoneList::getName($areaId);
}
}
}
@@ -473,24 +504,25 @@ class ItemBaseResponse extends TemplateResponse implements ICache
// tabs: this item contains..
$sourceFor = array(
[LOOT_ITEM, $this->subject->id, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
[LOOT_PROSPECTING, $this->subject->id, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_MILLING, $this->subject->id, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[LOOT_DISENCHANT, $this->subject->getField('disenchantId'), '$LANG.tab_disenchanting', 'disenchanting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']]
[Loot::ITEM, $this->typeId, '$LANG.tab_contains', 'contains', ['$Listview.extraCols.percent'], [] ],
[Loot::PROSPECTING, $this->typeId, '$LANG.tab_prospecting', 'prospecting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[Loot::MILLING, $this->typeId, '$LANG.tab_milling', 'milling', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']],
[Loot::DISENCHANT, $this->subject->getField('disenchantId'), '$LANG.tab_disenchanting', 'disenchanting', ['$Listview.extraCols.percent'], ['side', 'slot', 'reqlevel']]
);
foreach ($sourceFor as [$lootTemplate, $lootId, $tabName, $tabId, $extraCols, $hiddenCols])
{
$lootTab = new Loot();
if ($lootTab->getByContainer($lootTemplate, $lootId))
$lootTab = new LootByContainer();
if ($lootTab->getByContainer($lootTemplate, [$lootId]))
{
$this->extendGlobalData($lootTab->jsGlobals);
$extraCols = array_merge($extraCols, $lootTab->extraCols);
$tabData = array(
'data' => $lootTab->getResult(),
'name' => $tabName,
'id' => $tabId,
'data' => $lootTab->getResult(),
'name' => $tabName,
'id' => $tabId,
'computeDataFunc' => '$Listview.funcBox.initLootTable'
);
if ($extraCols)
@@ -585,12 +617,14 @@ class ItemBaseResponse extends TemplateResponse implements ICache
), SpellList::$brickFile));
}
// tab: unlocks (object or item) - LOCK_TYPE_ITEM: 1
// tab: unlocks (object or item)
$lockIds = DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = 1 AND `properties1` = ?d) OR
(`type2` = 1 AND `properties2` = ?d) OR (`type3` = 1 AND `properties3` = ?d) OR
(`type4` = 1 AND `properties4` = ?d) OR (`type5` = 1 AND `properties5` = ?d)',
$this->typeId, $this->typeId, $this->typeId, $this->typeId, $this->typeId
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_ITEM, $this->typeId, LOCK_TYPE_ITEM, $this->typeId,
LOCK_TYPE_ITEM, $this->typeId, LOCK_TYPE_ITEM, $this->typeId,
LOCK_TYPE_ITEM, $this->typeId
);
if ($lockIds)
@@ -621,40 +655,6 @@ class ItemBaseResponse extends TemplateResponse implements ICache
}
}
// tab: see also
$conditions = array(
['id', $this->typeId, '!'],
[
'OR',
['name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)],
[
'AND',
['class', $_class],
['subClass', $_subClass],
['slot', $_slot],
['itemLevel', $_ilvl - 15, '>'],
['itemLevel', $_ilvl + 15, '<'],
['quality', $this->subject->getField('quality')],
['requiredClass', $this->subject->getField('requiredClass') ?: -1] // todo: fix db data in setup and not on fetch
]
]
);
if ($_ = $this->subject->getField('itemset'))
$conditions[1][] = ['AND', ['slot', $_slot], ['itemset', $_]];
$saItems = new ItemList($conditions);
if (!$saItems->error)
{
$this->extendGlobalData($saItems->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $saItems->getListviewData(),
'name' => '$LANG.tab_seealso',
'id' => 'see-also'
), ItemList::$brickFile));
}
// tab: starts (quest)
if ($qId = $this->subject->getField('startQuest'))
{
@@ -707,28 +707,10 @@ class ItemBaseResponse extends TemplateResponse implements ICache
), QuestList::$brickFile));
}
// tab: same model as
// todo (low): should also work for creatures summoned by item
if (($model = $this->subject->getField('model')) && $_slot)
{
$sameModel = new ItemList(array(['model', $model], ['id', $this->typeId, '!'], ['slot', $_slot]));
if (!$sameModel->error)
{
$this->extendGlobalData($sameModel->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $sameModel->getListviewData(ITEMINFO_MODEL),
'name' => '$LANG.tab_samemodelas',
'id' => 'same-model-as',
'genericlinktype' => 'item'
), 'genericmodel'));
}
}
// tab: sold by
if (!empty($this->subject->getExtendedCost()[$this->subject->id]))
if (!empty($this->subject->getExtendedCost()[$this->typeId]))
{
$vendors = $this->subject->getExtendedCost()[$this->subject->id];
$vendors = $this->subject->getExtendedCost()[$this->typeId];
$soldBy = new CreatureList(array(['id', array_keys($vendors)]));
if (!$soldBy->error)
{
@@ -736,13 +718,14 @@ class ItemBaseResponse extends TemplateResponse implements ICache
if ($vendorSpawns = $soldBy->getSpawns(SPAWNINFO_FULL, true, true, true, true))
{
$this->map = array(
['parent' => 'mapper-generic'], // Mapper
$vendorSpawns, // mapperData
null, // ShowOnMap
[Lang::item('purchasedIn')] // foundIn
['parent' => 'mapper-generic'], // Mapper
$vendorSpawns, // mapperData
null, // ShowOnMap
[Lang::item('purchasedIn')], // foundIn
Lang::item('vendorLoc') // title
);
foreach ($vendorSpawns as $areaId => $_)
$this->map['extra'][$areaId] = ZoneList::getName($areaId);
$this->map[3][$areaId] = ZoneList::getName($areaId);
}
$sbData = $soldBy->getListviewData();
@@ -751,7 +734,7 @@ class ItemBaseResponse extends TemplateResponse implements ICache
$extraCols = ['$Listview.extraCols.stock', "\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost'];
$cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_NPC_VENDOR)->prepare();
$cnd->getBySource(Conditions::SRC_NPC_VENDOR, entry: $this->typeId)->prepare();
foreach ($sbData as $k => &$row)
{
$currency = [];
@@ -892,6 +875,58 @@ class ItemBaseResponse extends TemplateResponse implements ICache
}
}
// tab: see also
$conditions = array(
['id', $this->typeId, '!'],
[
'OR',
['name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)],
[
'AND',
['class', $_class],
['subClass', $_subClass],
['slot', $_slot],
['itemLevel', $_ilvl - 15, '>'],
['itemLevel', $_ilvl + 15, '<'],
['quality', $this->subject->getField('quality')],
['requiredClass', $this->subject->getField('requiredClass') ?: -1] // todo: fix db data in setup and not on fetch
]
]
);
if ($_ = $this->subject->getField('itemset'))
$conditions[1][] = ['AND', ['slot', $_slot], ['itemset', $_]];
$saItems = new ItemList($conditions);
if (!$saItems->error)
{
$this->extendGlobalData($saItems->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $saItems->getListviewData(),
'name' => '$LANG.tab_seealso',
'id' => 'see-also'
), ItemList::$brickFile));
}
// tab: same model as
// todo (low): should also work for creatures summoned by item
if (($model = $this->subject->getField('model')) && $_slot)
{
$sameModel = new ItemList(array(['model', $model], ['id', $this->typeId, '!'], ['slot', $_slot]));
if (!$sameModel->error)
{
$this->extendGlobalData($sameModel->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $sameModel->getListviewData(ITEMINFO_MODEL),
'name' => '$LANG.tab_samemodelas',
'id' => 'same-model-as',
'genericlinktype' => 'item'
), 'genericmodel'));
}
}
// tab: Shared cooldown
$cdCats = [];
$useSpells = [];

View File

@@ -192,7 +192,7 @@ class ItemXmlResponse extends TextResponse implements ICache
}
// link
$xml->addChild('link', Cfg::get('HOST_URL').'?item='.$this->subject->id);
$xml->addChild('link', Cfg::get('HOST_URL').'?item='.$this->typeId);
$this->result = $root->asXML();
}
@@ -220,8 +220,7 @@ class ItemXmlResponse extends TextResponse implements ICache
{
return array(
$this->type, // DBType
$this->typeId, // DBTypeId
-1, // category
$this->typeId, // DBTypeId/category
-1, // staff mask (content does not diff)
'' // misc (unused)
);

View File

@@ -11,7 +11,7 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ITEM;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'items';
protected string $pageName = 'items';
@@ -99,6 +99,12 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ItemListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -110,14 +116,9 @@ class ItemsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
$this->filter->evalWeights();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*******************/
/* evaluate filter */

View File

@@ -10,7 +10,7 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'itemset';
protected string $pageName = 'itemset';
@@ -95,15 +95,7 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
// itemLevel
if ($min = $this->subject->getField('minLevel'))
{
$foo = Lang::game('level').Lang::main('colon').$min;
$max = $this->subject->getField('maxLevel');
if ($min < $max)
$foo .= ' - '.$max;
$infobox[] = $foo;
}
$infobox[] = Lang::game('level').Lang::main('colon').Util::createNumRange($min, $this->subject->getField('maxLevel'), ' - ');
// class
if ($cl = Lang::getClassString($this->subject->getField('classMask'), $jsg, Lang::FMT_MARKUP))
@@ -125,6 +117,13 @@ class ItemsetBaseResponse extends TemplateResponse implements ICache
if ($_ta)
$infobox[] = Lang::itemset('_tag').'[url=?itemsets&filter=ta='.$_ta.']'.Lang::itemset('notes', $_ta).'[/url]';
// id
$infobox[] = Lang::itemset('id') . $this->subject->getField('refSetId');
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ITEMSET;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'itemsets';
protected string $pageName = 'itemsets';
@@ -32,6 +32,12 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ItemsetListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -43,13 +49,9 @@ class ItemsetsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -14,15 +14,22 @@ class LatestcommentsRssResponse extends TextResponse
protected function generate() : void
{
foreach (CommunityContent::getCommentPreviews(dateFmt: false) as $comment)
$now = new DateTime();
foreach (CommunityContent::getCommentPreviews(['comments' => 1, 'replies' => 1], dateFmt: false) as $comment)
{
if (empty($comment['commentid']))
$url = Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id'];
else
$url = Cfg::get('HOST_URL').'/?go-to-reply&amp;id='.$comment['id'];
// todo (low): preview should be html-formated
$this->feedData[] = array(
'title' => [true, [], Lang::typeName($comment['type']).Lang::main('colon').htmlentities($comment['subject'])],
'link' => [false, [], Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id']],
'description' => [true, [], htmlentities($comment['preview'])."<br /><br />".Lang::main('byUser', [$comment['user'], '']) . Util::formatTimeDiff($comment['date'])],
'link' => [false, [], $url],
'description' => [true, [], htmlentities($comment['preview'])."<br /><br />".Lang::main('byUser', [$comment['user'], '']) . $now->formatDate($comment['date'], true)],
'pubDate' => [false, [], date(DATE_RSS, $comment['date'])],
'guid' => [false, [], Cfg::get('HOST_URL').'/?go-to-comment&amp;id='.$comment['id']]
'guid' => [false, [], $url]
// 'domain' => [false, [], null]
);
}

View File

@@ -14,12 +14,14 @@ class LatestscreenshotsRssResponse extends TextResponse
protected function generate() : void
{
$now = new DateTime();
foreach (CommunityContent::getScreenshots(dateFmt: false) as $screenshot)
{
$desc = '<a href="'.Cfg::get('HOST_URL').'/?'.Type::getFileString($screenshot['type']).'='.$screenshot['typeId'].'#screenshots:id='.$screenshot['id'].'"><img src="'.Cfg::get('STATIC_URL').'/uploads/screenshots/thumb/'.$screenshot['id'].'.jpg" alt="" /></a>';
if ($screenshot['caption'])
$desc .= '<br />'.$screenshot['caption'];
$desc .= "<br /><br />".Lang::main('byUser', [$screenshot['user'], '']) . Util::formatTimeDiff($screenshot['date'], true);
$desc .= "<br /><br />".Lang::main('byUser', [$screenshot['user'], '']) . $now->formatDate($screenshot['date'], true);
// enclosure/length => filesize('static/uploads/screenshots/thumb/'.$screenshot['id'].'.jpg') .. always set to this placeholder value though
$this->feedData[] = array(

View File

@@ -14,12 +14,14 @@ class LatestvideosRssResponse extends TextResponse
protected function generate() : void
{
$now = new DateTime();
foreach (CommunityContent::getvideos(dateFmt: false) as $video)
{
$desc = '<a href="'.Cfg::get('HOST_URL').'/?'.Type::getFileString($video['type']).'='.$video['typeId'].'#videos:id='.$video['id'].'"><img src="//i3.ytimg.com/vi/'.$video['videoId'].'/default.jpg" alt="" /></a>';
if ($video['caption'])
$desc .= '<br />'.$video['caption'];
$desc .= "<br /><br />".Lang::main('byUser', [$video['user'], '']) . Util::formatTimeDiff($video['date'], true);
$desc .= "<br /><br />".Lang::main('byUser', [$video['user'], '']) . $now->formatDate($video['date'], true);
// is enclosure/length .. is this even relevant..?
$this->feedData[] = array(

View File

@@ -9,7 +9,7 @@ if (!defined('AOWOW_REVISION'))
class LocaleBaseResponse extends TextResponse
{
protected array $expectedGET = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom']]
'locale' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkLocale']]
);
protected function generate() : void

View File

@@ -10,7 +10,7 @@ class MailBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'mail';
@@ -99,7 +99,7 @@ class MailBaseResponse extends TemplateResponse implements ICache
}
if ($q['rewardMailDelay'] > 0)
$infobox[] = Lang::mail('delay', [Util::formatTime($q['rewardMailDelay'] * 1000)]);
$infobox[] = Lang::mail('delay', [DateTime::formatTimeElapsed($q['rewardMailDelay'] * 1000)]);
}
else if ($npcId = DB::World()->selectCell('SELECT `Sender` FROM achievement_reward WHERE `MailTemplateId` = ?d', $this->typeId))
{
@@ -108,6 +108,9 @@ class MailBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::mail('id') . $this->typeId;
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class MailsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::MAIL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'mails';

View File

@@ -8,8 +8,6 @@ if (!defined('AOWOW_REVISION'))
class MapsBaseResponse extends TemplateResponse
{
protected int $cacheType = CACHE_TYPE_PAGE;
protected string $template = 'maps';
protected string $pageName = 'maps';
protected ?int $activeTab = parent::TAB_TOOLS;

View File

@@ -10,7 +10,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'npc';
protected string $pageName = 'npc';
@@ -103,18 +103,17 @@ class NpcBaseResponse extends TemplateResponse implements ICache
/**********************/
$mapType = 0;
if ($maps = DB::Aowow()->selectCol('SELECT DISTINCT `areaId` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $this->typeId))
if ($maps = DB::Aowow()->selectCell('SELECT IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $this->typeId))
{
if (count($maps) == 1) // should only exist in one instance
$mapType = match (DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0]))
{
// MAP_TYPE_DUNGEON,
MAP_TYPE_DUNGEON_HC => 1,
// MAP_TYPE_RAID,
MAP_TYPE_MMODE_RAID,
MAP_TYPE_MMODE_RAID_HC => 2,
default => 0
};
$mapType = match ((int)DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps[0]))
{
// MAP_TYPE_DUNGEON,
MAP_TYPE_DUNGEON_HC => 1,
// MAP_TYPE_RAID,
MAP_TYPE_MMODE_RAID,
MAP_TYPE_MMODE_RAID_HC => 2,
default => 0
};
}
// npc is difficulty dummy: get max difficulty from parent npc
if ($this->placeholder && ($mt = DB::Aowow()->selectCell('SELECT IF(`difficultyEntry1` = ?d, 1, 2) FROM ?_creature WHERE `difficultyEntry1` = ?d OR `difficultyEntry2` = ?d OR `difficultyEntry3` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId)))
@@ -194,6 +193,13 @@ class NpcBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('npcflag') & (NPC_FLAG_SPIRIT_HEALER | NPC_FLAG_SPIRIT_GUIDE))
$infobox[] = Lang::npc('extraFlags', CREATURE_FLAG_EXTRA_GHOST_VISIBILITY);
// id
$infobox[] = Lang::npc('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if (User::isInGroup(U_GROUP_EMPLOYEE))
{
// AI
@@ -237,7 +243,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
}
if ($stats = $this->getCreatureStats($mapType, $_altIds))
$infobox[] = Lang::npc('stats').($_altIds ? ' ('.Lang::npc('modes', $mapType, 0).')' : '').Lang::main('colon').'[ul][li]'.implode('[/li][li]', $stats).'[/li][/ul]';
$infobox[] = Lang::npc('stats').($_altIds ? ' ('.Lang::game('modes', $mapType, 0).')' : '').Lang::main('colon').'[ul][li]'.implode('[/li][li]', $stats).'[/li][/ul]';
if ($infobox)
{
@@ -312,6 +318,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
// tab: abilities / tab_controlledabilities (dep: VehicleId)
$tplSpells = [];
$genSpells = [];
$spellClick = [];
$conditions = ['OR'];
for ($i = 1; $i < 9; $i++)
@@ -333,6 +340,12 @@ class NpcBaseResponse extends TemplateResponse implements ICache
if ($genSpells)
$conditions[] = ['id', $genSpells];
if ($spellClick = DB::World()->select('SELECT `spell_id` AS ARRAY_KEY, `cast_flags` AS "0", `user_type` AS "1" FROM npc_spellclick_spells WHERE `npc_entry` = ?d', $this->typeId))
{
$genSpells = array_merge($genSpells, array_keys($spellClick));
$conditions[] = ['id', array_keys($spellClick)];
}
// Pet-Abilities
if (($_typeFlags & NPC_TYPEFLAG_TAMEABLE) && ($_ = $this->subject->getField('family')))
{
@@ -370,6 +383,9 @@ class NpcBaseResponse extends TemplateResponse implements ICache
foreach ($controled as $id => $values)
{
if (isset($spellClick[$id]))
$values['spellclick'] = $spellClick[$id];
if (in_array($id, $genSpells))
{
$normal[$id] = $values;
@@ -379,7 +395,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
}
$cnd = new Conditions();
$cnd->getBySourceGroup($this->typeId, Conditions::SRC_VEHICLE_SPELL)->prepare();
$cnd->getBySource(Conditions::SRC_VEHICLE_SPELL, group: $this->typeId)->prepare();
if ($cnd->toListviewColumn($controled, $extraCols, $this->typeId, 'id'))
$this->extendGlobalData($cnd->getJsGlobals());
@@ -526,7 +542,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$soldItems = new ItemList(array(['id', $sells]));
if (!$soldItems->error)
{
$colAddIn = null;
$colAddIn = '';
$extraCols = ["\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost'];
$lvData = $soldItems->getListviewData(ITEMINFO_VENDOR, [Type::NPC => [$this->typeId]]);
@@ -541,7 +557,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
}
$cnd = new Conditions();
if ($cnd->getBySourceGroup($this->typeId, Conditions::SRC_NPC_VENDOR)->prepare())
if ($cnd->getBySource(Conditions::SRC_NPC_VENDOR, group: $this->typeId)->prepare())
{
$this->extendGlobalData($cnd->getJsGlobals());
$cnd->toListviewColumn($lvData, $extraCols, $this->typeId, 'id');
@@ -559,79 +575,108 @@ class NpcBaseResponse extends TemplateResponse implements ICache
}
// tabs: this creature contains..
$skinTab = ['tab_skinning', 'skinning', SKILL_SKINNING];
if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_HERBALISM)
$skinTab = ['tab_herbalism', 'herbalism', SKILL_HERBALISM];
else if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_MINING)
$skinTab = ['tab_mining', 'mining', SKILL_MINING];
else if ($_typeFlags & NPC_TYPEFLAG_SKIN_WITH_ENGINEERING)
$skinTab = ['tab_engineering', 'engineering', SKILL_ENGINEERING];
/*
extraCols: [Listview.extraCols.count, Listview.extraCols.percent, Listview.extraCols.mode],
_totalCount: 22531,
computeDataFunc: Listview.funcBox.initLootTable,
onAfterCreate: Listview.funcBox.addModeIndicator,
modes:{"mode":1,"1":{"count":4408,"outof":16013},"4":{"count":4408,"outof":22531}}
*/
if ($this->subject->isGatherable())
$skinTab = ['$LANG.tab_herbalism', 'herbalism', SKILL_HERBALISM];
else if ($this->subject->isMineable())
$skinTab = ['$LANG.tab_mining', 'mining', SKILL_MINING];
else if ($this->subject->isSalvageable())
$skinTab = ['$LANG.tab_engineering', 'engineering', SKILL_ENGINEERING];
else
$skinTab = ['$LANG.tab_skinning', 'skinning', SKILL_SKINNING];
$sourceFor = array(
0 => [LOOT_CREATURE, $this->subject->getField('lootId'), '$LANG.tab_drops', 'drops', [ ], ''],
8 => [LOOT_PICKPOCKET, $this->subject->getField('pickpocketLootId'), '$LANG.tab_pickpocketing', 'pickpocketing', ['side', 'slot', 'reqlevel'], ''],
9 => [LOOT_SKINNING, $this->subject->getField('skinLootId'), '$LANG.'.$skinTab[0], $skinTab[1], ['side', 'slot', 'reqlevel'], '']
0 => [Loot::CREATURE, [4 => $this->subject->getField('lootId')], '$LANG.tab_drops', 'drops', [ ], ''],
1 => [Loot::GAMEOBJECT, [], '$LANG.tab_drops', 'drops-object', [ ], ''],
2 => [Loot::PICKPOCKET, [4 => $this->subject->getField('pickpocketLootId')], '$LANG.tab_pickpocketing', 'pickpocketing', ['side', 'slot', 'reqlevel'], ''],
3 => [Loot::SKINNING, [4 => $this->subject->getField('skinLootId')], $skinTab[0], $skinTab[1], ['side', 'slot', 'reqlevel'], '']
);
// temp: manually add loot for difficulty-versions
$langref = array(
"-2" => '$LANG.tab_heroic',
"-1" => '$LANG.tab_normal',
1 => '$$WH.sprintf(LANG.tab_normalX, 10)',
2 => '$$WH.sprintf(LANG.tab_normalX, 25)',
3 => '$$WH.sprintf(LANG.tab_heroicX, 10)',
4 => '$$WH.sprintf(LANG.tab_heroicX, 25)'
);
/* loot tabs to sub tabs
* (1 << 0) => '$LANG.tab_heroic',
* (1 << 1) => '$LANG.tab_normal',
* (1 << 2) => '$LANG.tab_drops',
* (1 << 3) => '$$WH.sprintf(LANG.tab_normalX, 10)',
* (1 << 4) => '$$WH.sprintf(LANG.tab_normalX, 25)',
* (1 << 5) => '$$WH.sprintf(LANG.tab_heroicX, 10)',
* (1 << 6) => '$$WH.sprintf(LANG.tab_heroicX, 25)'
*/
$getBit = function(int $type, int $difficulty) : int
{
if ($type == 1) // dungeon
return 1 << (2 - $difficulty);
if ($type == 2) // raid
return 1 << (2 + $difficulty);
return 4; // generic case
};
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d ORDER BY `difficulty` ASC', $this->typeId) as $difficulty => $lgo)
{
$sourceFor[1][1][$getBit($mapType, $difficulty)] = $lgo['lootId'];
$sourceFor[1][5] = $sourceFor[1][5] ?: '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")';
}
if ($_altIds)
{
$sourceFor[0][2] = $mapType == 1 ? $langref[-1] : $langref[1];
if ($mapType == 1) // map generic loot to dungeon NH
{
$sourceFor[0][1] = [2 => $sourceFor[0][1][4]];
$sourceFor[2][1] = [2 => $sourceFor[2][1][4]];
$sourceFor[3][1] = [2 => $sourceFor[3][1][4]];
}
if ($mapType == 2) // map generic loot to raid 10NH
{
$sourceFor[0][1] = [8 => $sourceFor[0][1][4]];
$sourceFor[2][1] = [8 => $sourceFor[2][1][4]];
$sourceFor[3][1] = [8 => $sourceFor[3][1][4]];
}
foreach ($this->altNPCs->iterate() as $id => $__)
{
$mode = ($_altIds[$id] + 1) * ($mapType == 1 ? -1 : 1);
foreach (DB::Aowow()->select('SELECT o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8`, l.`difficulty` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d', $id) as $l)
$sourceFor[(($l['difficulty'] - 1) * 2) + 1] = [LOOT_GAMEOBJECT, $l['lootId'], $langref[$l['difficulty'] * ($mapType == 1 ? -1 : 1)], 'drops-object-'.$l['difficulty'], [], '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$l['id'].', "'.Util::localizedString($l, 'name').'")'];
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d ORDER BY `difficulty` ASC', $id) as $difficulty => $lgo)
{
$sourceFor[1][1][$getBit($mapType, $difficulty)] = $lgo['lootId'];
$sourceFor[1][5] = $sourceFor[1][5] ?: '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")';
}
if ($lootId = $this->altNPCs->getField('lootId'))
$sourceFor[($mode - 1) * 2] = [LOOT_CREATURE, $lootId, $langref[$mode], 'drops-'.abs($mode), [], ''];
$sourceFor[0][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
if ($lootId = $this->altNPCs->getField('pickpocketLootId'))
$sourceFor[2][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
if ($lootId = $this->altNPCs->getField('skinLootId'))
$sourceFor[3][1][$getBit($mapType, $_altIds[$id] + 1)] = $lootId;
}
}
foreach (DB::Aowow()->select('SELECT l.`difficulty` AS ARRAY_KEY, o.`id`, o.`lootId`, o.`name_loc0`, o.`name_loc2`, o.`name_loc3`, o.`name_loc4`, o.`name_loc6`, o.`name_loc8` FROM ?_loot_link l JOIN ?_objects o ON o.`id` = l.`objectId` WHERE l.`npcId` = ?d', $this->typeId) as $difficulty => $lgo)
$sourceFor[(($difficulty - 1) * 2) + 1] = [LOOT_GAMEOBJECT, $lgo['lootId'], $mapType ? $langref[$difficulty * ($mapType == 1 ? -1 : 1)] : '$LANG.tab_drops', 'drops-object-'.$difficulty, [], '$$WH.sprintf(LANG.lvnote_npcobjectsource, '.$lgo['id'].', "'.Util::localizedString($lgo, 'name').'")'];
ksort($sourceFor);
foreach ($sourceFor as [$lootTpl, $lootId, $tabName, $tabId, $hiddenCols, $note])
foreach ($sourceFor as [$lootTpl, $lootEntries, $tabName, $tabId, $hiddenCols, $note])
{
$creatureLoot = new Loot();
if ($creatureLoot->getByContainer($lootTpl, $lootId))
$creatureLoot = new LootByContainer();
if ($creatureLoot->getByContainer($lootTpl, $lootEntries))
{
$extraCols = $creatureLoot->extraCols;
$extraCols[] = '$Listview.extraCols.percent';
array_push($extraCols, '$Listview.extraCols.count', '$Listview.extraCols.percent');
if (count($lootEntries) > 1)
$extraCols[] = '$Listview.extraCols.mode';
$hiddenCols[] = 'count';
$this->extendGlobalData($creatureLoot->jsGlobals);
$tabData = array(
'data' => $creatureLoot->getResult(),
'name' => $tabName,
'id' => $tabId,
'extraCols' => array_unique($extraCols),
'hiddenCols' => $hiddenCols ?: null,
'sort' => ['-percent', 'name']
'data' => $creatureLoot->getResult(),
'id' => $tabId,
'name' => $tabName,
'extraCols' => array_unique($extraCols),
'hiddenCols' => $hiddenCols ?: null,
'sort' => ['-percent', 'name'],
'_totalCount' => 10000,
'computeDataFunc' => '$Listview.funcBox.initLootTable',
'onAfterCreate' => '$Listview.funcBox.addModeIndicator',
);
if ($note)
$tabData['note'] = $note;
else if ($lootTpl == LOOT_SKINNING)
else if ($lootTpl == Loot::SKINNING)
$tabData['note'] = '<b>'.Lang::formatSkillBreakpoints(Game::getBreakpointsForSkill($skinTab[2], $this->subject->getField('maxLevel') * 5), Lang::FMT_HTML).'</b>';
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile));
@@ -770,8 +815,8 @@ class NpcBaseResponse extends TemplateResponse implements ICache
// tab: conditions
$cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_CREATURE_TEMPLATE_VEHICLE)
->getBySourceGroup($this->typeId, Conditions::SRC_SPELL_CLICK_EVENT)
$cnd->getBySource(Conditions::SRC_CREATURE_TEMPLATE_VEHICLE, entry: $this->typeId)
->getBySource(Conditions::SRC_SPELL_CLICK_EVENT, group: $this->typeId)
->getByCondition(Type::NPC, $this->typeId)
->prepare();
if ($tab = $cnd->toListviewTab())
@@ -825,7 +870,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$spill[0][1] = $set[1][1] / 2;
$spillover[$factions->getField('cat')] = $spill;
$set[6] = $factions->getField('cat'); // set spillover
$set[5] = $factions->getField('cat'); // set spillover
}
$result[] = $set;
@@ -841,7 +886,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
// base NPC
if ($base = $this->getRepForId([$this->typeId], $spilledParents))
$reputation[] = [Lang::npc('modes', 1, 0), $base];
$reputation[] = [Lang::game('modes', 1, 0), $base];
// difficulty dummys
if ($dummyIds && ($mapType == 1 || $mapType == 2))
@@ -855,7 +900,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
// apply by difficulty
foreach ($alt as $mode => $dat)
$reputation[] = [Lang::npc('modes', $mapType, $mode), $dat];
$reputation[] = [Lang::game('modes', $mapType, $mode), $dat];
}
// get spillover factions and apply
@@ -865,7 +910,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
foreach ($reputation as $i => [, $data])
{
foreach ($data as [$factionId, , , , , , $spillover])
foreach ($data as [$factionId, , , , , $spillover])
{
if (!$spillover)
continue;
@@ -957,7 +1002,7 @@ class NpcBaseResponse extends TemplateResponse implements ICache
if (!$this->altNPCs->getEntry($id))
continue;
$m = Lang::npc('modes', $mapType, $mode);
$m = Lang::game('modes', $mapType, $mode);
// Health
$health = $this->altNPCs->getBaseStats('health');
@@ -999,10 +1044,13 @@ class NpcBaseResponse extends TemplateResponse implements ICache
$modes['ranged'][] = sprintf($modeRow, $m, Lang::nf($ranged[0]).' - '.Lang::nf($ranged[1]));
}
// todo: resistances can be present/missing in either $stats or $modes
// should be handled separately..?
if ($modes)
foreach ($stats as $k => $v)
if ($v)
$stats[$k] = sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k);
$stats[$k] = isset($modes[$k]) ? sprintf($hint, implode('[/tr][tr]', $modes[$k]), $v, $k) : $v;
return $stats;
}

View File

@@ -11,7 +11,7 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::NPC;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'npcs';
protected string $pageName = 'npcs';
@@ -35,6 +35,12 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new CreatureListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -46,13 +52,9 @@ class NpcsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
if ($this->category)
{
$conditions[] = ['type', $this->category[0]];

View File

@@ -10,7 +10,7 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'object';
protected string $pageName = 'object';
@@ -22,6 +22,9 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
public ?Book $book = null;
public ?array $relBoss = null;
private array $difficulties = [];
private int $mapType = 0;
private GameObjectList $subject;
public function __construct(string $id)
@@ -61,6 +64,37 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
array_unshift($this->title, Lang::unescapeUISequences($this->subject->getField('name', true), Lang::FMT_RAW), Util::ucFirst(Lang::game('object')));
/**********************/
/* Determine Map Type */
/**********************/
if ($objectdifficulty = DB::Aowow()->select( // has difficulty versions of itself
'SELECT `normal10` AS "0", `normal25` AS "1",
`heroic10` AS "2", `heroic25` AS "3",
`mapType` AS ARRAY_KEY
FROM ?_objectdifficulty
WHERE `normal10` = ?d OR `normal25` = ?d OR
`heroic10` = ?d OR `heroic25` = ?d',
$this->typeId, $this->typeId, $this->typeId, $this->typeId
))
{
$this->mapType = key($objectdifficulty);
$this->difficulties = array_pop($objectdifficulty);
}
else if ($maps = DB::Aowow()->selectCell('SELECT IF(COUNT(DISTINCT `areaId`) > 1, 0, `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::OBJECT, $this->typeId))
{
$this->mapType = match ((int)DB::Aowow()->selectCell('SELECT `type` FROM ?_zones WHERE `id` = ?d', $maps))
{
// MAP_TYPE_DUNGEON,
MAP_TYPE_DUNGEON_HC => 1,
// MAP_TYPE_RAID,
MAP_TYPE_MMODE_RAID,
MAP_TYPE_MMODE_RAID_HC => 2,
default => 0
};
}
/***********/
/* Infobox */
/***********/
@@ -148,12 +182,10 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('lootStack'))
{
[$min, $max, $restock] = $this->subject->getField('lootStack');
$buff = Lang::spell('spellModOp', 4).Lang::main('colon').$min;
if ($min < $max)
$buff .= Lang::game('valueDelim').$max;
$buff = Lang::spell('spellModOp', 4).Lang::main('colon').Util::createNumRange($min, $max);
// since Veins don't have charges anymore, the timer is questionable
$infobox[] = $restock > 1 ? '[tooltip name=restock]'.Lang::gameObject('restock', [Util::formatTime($restock * 1000)]).'[/tooltip][span class=tip tooltip=restock]'.$buff.'[/span]' : $buff;
$infobox[] = $restock > 1 ? '[tooltip name=restock]'.Lang::gameObject('restock', [DateTime::formatTimeElapsed($restock * 1000)]).'[/tooltip][span class=tip tooltip=restock]'.$buff.'[/span]' : $buff;
}
// meeting stone [minLevel, maxLevel, zone]
@@ -163,10 +195,7 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
$this->extendGlobalIds(Type::ZONE, $zone);
$m = Lang::game('meetingStone').'[zone='.$zone.']';
$l = $minLevel;
if ($minLevel > 1 && $maxLevel > $minLevel)
$l .= Lang::game('valueDelim').min($maxLevel, MAX_LEVEL);
$l = Util::createNumRange($minLevel, min($maxLevel, MAX_LEVEL));
$infobox[] = $l ? '[tooltip name=meetingstone]'.Lang::game('reqLevel', [$l]).'[/tooltip][span class=tip tooltip=meetingstone]'.$m.'[/span]' : $m;
}
@@ -181,11 +210,11 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
if ($minTime > 1 || $minPlayer || $radius)
$buff .= Lang::main('colon').'[ul]';
if ($minTime > 1)
$buff .= '[li]'.Lang::game('duration').Lang::main('colon').($maxTime > $minTime ? Util::FormatTime($maxTime * 1000, true).' - ' : '').Util::FormatTime($minTime * 1000, true).'[/li]';
if ($minTime > 1) // sign shenannigans reverse the display order
$buff .= '[li]'.Lang::game('duration').Lang::main('colon').Util::createNumRange(-$maxTime, -$minTime, fn: fn($x) => DateTime::formatTimeElapsed(-$x * 1000)).'[/li]';
if ($minPlayer)
$buff .= '[li]'.Lang::main('players').Lang::main('colon').$minPlayer.($maxPlayer > $minPlayer ? ' - '.$maxPlayer : '').'[/li]';
$buff .= '[li]'.Lang::main('players').Lang::main('colon').Util::createNumRange($minPlayer, $maxPlayer).'[/li]';
if ($radius)
$buff .= '[li]'.Lang::spell('range', [$radius]).'[/li]';
@@ -196,6 +225,18 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
$infobox[] = $buff;
}
// id
$infobox[] = Lang::gameObject('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
// used in mode
foreach ($this->difficulties as $n => $id)
if ($id == $this->typeId)
$infobox[] = Lang::game('mode').Lang::game('modes', $this->mapType, $n);
// AI
if (User::isInGroup(U_GROUP_EMPLOYEE))
if ($_ = $this->subject->getField('ScriptOrAI'))
@@ -400,12 +441,37 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
// tab: contains
if ($_ = $this->subject->getField('lootId'))
{
$goLoot = new Loot();
if ($goLoot->getByContainer(LOOT_GAMEOBJECT, $_))
// check if loot_link entry exists (only difficulty: 1)
if ($npcId = DB::Aowow()->selectCell('SELECT `npcId` FROM ?_loot_link WHERE `objectId` = ?d AND `difficulty` = 1', $this->typeId))
{
$extraCols = $goLoot->extraCols;
$extraCols[] = '$Listview.extraCols.percent';
$hiddenCols = ['source', 'side', 'slot', 'reqlevel'];
// get id set of npc
$lootEntries = DB::Aowow()->selectCol(
'SELECT ll.`difficulty` AS ARRAY_KEY, o.`lootId`
FROM ?_creature c
LEFT JOIN ?_loot_link ll ON ll.`npcId` IN (c.`id`, c.`difficultyEntry1`, c.`difficultyEntry2`, c.`difficultyEntry3`)
LEFT JOIN ?_objects o ON o.`id` = ll.`objectId`
WHERE c.`id` = ?d
ORDER BY ll.`difficulty` ASC',
$npcId
);
if ($this->mapType == 2 || count($lootEntries) > 2) // always raid
$lootEntries = array_combine(array_map(fn($x) => 1 << (2 + $x), array_keys($lootEntries)), array_values($lootEntries));
else if ($this->mapType == 1 || count($lootEntries) == 2) // dungeon or raid, assume dungeon
$lootEntries = array_combine(array_map(fn($x) => 1 << (2 - $x), array_keys($lootEntries)), array_values($lootEntries));
}
else
$lootEntries = [4 => $_];
$goLoot = new LootByContainer();
if ($goLoot->getByContainer(Loot::GAMEOBJECT, $lootEntries))
{
$extraCols = $goLoot->extraCols;
array_push($extraCols, '$Listview.extraCols.count', '$Listview.extraCols.percent');
if (count($lootEntries) > 1)
$extraCols[] = '$Listview.extraCols.mode';
$hiddenCols = ['source', 'side', 'slot', 'reqlevel', 'count'];
$this->extendGlobalData($goLoot->jsGlobals);
$lootResult = $goLoot->getResult();
@@ -419,12 +485,16 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
}
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lootResult,
'id' => 'contains',
'name' => '$LANG.tab_contains',
'sort' => ['-percent', 'name'],
'extraCols' => array_unique($extraCols),
'hiddenCols' => $hiddenCols ?: null
'data' => $lootResult,
'id' => 'contains',
'name' => '$LANG.tab_contains',
'sort' => ['-percent', 'name'],
'extraCols' => array_unique($extraCols),
'hiddenCols' => $hiddenCols ?: null,
'sort' => ['-percent', 'name'],
'_totalCount' => 10000,
'computeDataFunc' => '$Listview.funcBox.initLootTable',
'onAfterCreate' => '$Listview.funcBox.addModeIndicator',
), ItemList::$brickFile));
}
}
@@ -469,6 +539,51 @@ class ObjectBaseResponse extends TemplateResponse implements ICache
), GameObjectList::$brickFile));
}
// tab: see also
if ($this->difficulties)
{
$conditions = array(
'AND',
['id', $this->difficulties],
['id', $this->typeId, '!']
);
$saObjects = new GameObjectList($conditions);
if (!$saObjects->error)
{
$data = $saObjects->getListviewData();
if ($this->difficulties)
{
$saE = ['$Listview.extraCols.mode'];
foreach ($data as $id => &$d)
{
if (($modeBit = array_search($id, $this->difficulties)) !== false)
{
if ($this->mapType)
$d['modes'] = ['mode' => 1 << ($modeBit + 3)];
else
$d['modes'] = ['mode' => 2 - $modeBit];
}
else
$d['modes'] = ['mode' => 0];
}
}
$tabData = array(
'data' => $data,
'id' => 'see-also',
'name' => '$LANG.tab_seealso',
'visibleCols' => ['level'],
);
if (isset($saE))
$tabData['extraCols'] = $saE;
$this->lvTabs->addListviewTab(new Listview($tabData, GameObjectList::$brickFile));
}
}
// tab: Same model as
$sameModel = new GameObjectList(array(['displayId', $this->subject->getField('displayId')], ['id', $this->typeId, '!']));
if (!$sameModel->error)

View File

@@ -11,7 +11,7 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::OBJECT;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'objects';
protected string $pageName = 'objects';
@@ -35,6 +35,12 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new GameObjectListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -46,16 +52,12 @@ class ObjectsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
if ($this->category)
$conditions[] = ['typeCat', (int)$this->category[0]];
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -10,7 +10,7 @@ class PetBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'pet';
@@ -73,13 +73,20 @@ class PetBaseResponse extends TemplateResponse implements ICache
if ($this->subject->getField('exotic'))
$infobox[] = '[url=?spell=53270]'.Lang::pet('exotic').'[/url]';
// id
$infobox[] = Lang::pet('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');

View File

@@ -11,7 +11,7 @@ class PetsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::PET;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'pets';

View File

@@ -37,10 +37,8 @@ class ProfileDeleteResponse extends TextResponse
// only flag as deleted; only custom profiles
DB::Aowow()->query(
'UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` | ?d WHERE `id` IN (?a) AND `cuFlags` & ?d {AND `user` = ?d}',
PROFILER_CU_DELETED,
'UPDATE ?_profiler_profiles SET `deleted` = 1 WHERE `id` IN (?a) AND `custom` = 1 {AND `user` = ?d}',
$this->_get['id'],
PROFILER_CU_PROFILE,
User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU) ? DBSIMPLE_SKIP : User::$id
);
}

View File

@@ -38,8 +38,8 @@ class ProfileLinkResponse extends TextResponse
// only link characters, not custom profiles
$newId = DB::Aowow()->query(
'REPLACE INTO ?_account_profiles (`accountId`, `profileId`, `extraFlags`)
SELECT ?d, p.`id`, 0 FROM ?_profiler_profiles p WHERE p.`id` = ?d AND (`cuFlags` & ?d) = 0',
User::$id, $this->_get['id'], PROFILER_CU_PROFILE
SELECT ?d, p.`id`, 0 FROM ?_profiler_profiles p WHERE p.`id` = ?d AND `custom` = 0',
User::$id, $this->_get['id']
);
if (!is_int($newId))

View File

@@ -47,7 +47,7 @@ class ProfileLoadResponse extends TextResponse
return;
}
if (($pBase['cuFlags'] & PROFILER_CU_DELETED) && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
if ($pBase['deleted'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return;
@@ -56,7 +56,7 @@ class ProfileLoadResponse extends TextResponse
if ($rId == $pBase['realm'])
break;
if (!$rData) // realm doesn't exist or access is restricted
if ($pBase['realm'] && !$rData) // realm doesn't exist or access is restricted
return;
$profile = array(
@@ -103,7 +103,7 @@ class ProfileLoadResponse extends TextResponse
'activity' => [], // recent raid activity [achievementId => 1] (is a subset of statistics)
);
if ($pBase['cuFlags'] & PROFILER_CU_PROFILE)
if ($pBase['custom'])
{
// this parameter is _really_ strange .. probably still not doing this right
$profile['source'] = $pBase['realm'] ? $pBase['sourceId'] : 0;
@@ -135,7 +135,7 @@ class ProfileLoadResponse extends TextResponse
$profile['pets'] = $pets;
// source for custom profiles; profileId => [name, ownerId, iconString(optional)]
if ($customs = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `name`, `user`, `icon` FROM ?_profiler_profiles WHERE `sourceId` = ?d AND `sourceId` <> `id` {AND (`cuFlags` & ?d) = 0}', $pBase['id'], User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : PROFILER_CU_DELETED))
if ($customs = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `name`, `user`, `icon` FROM ?_profiler_profiles WHERE `sourceId` = ?d AND `sourceId` <> `id` {AND `deleted` = ?d}', $pBase['id'], User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : 0))
{
foreach ($customs as $id => $cu)
{

View File

@@ -67,11 +67,11 @@ class ProfileBaseResponse extends TemplateResponse
// 3 possibilities
// 1) already synced to aowow
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `cuFlags` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucFirst($this->subjectName), $rnItr))
if ($subject = DB::Aowow()->selectRow('SELECT `id`, `realmGUID`, `stub` FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucFirst($this->subjectName), $rnItr))
{
$this->typeId = $subject['id'];
if ($subject['cuFlags'] & PROFILER_CU_NEEDS_RESYNC)
if ($subject['stub'])
$this->handleIncompleteData(Type::PROFILE, $subject['realmGUID']);
return;
@@ -82,27 +82,29 @@ class ProfileBaseResponse extends TemplateResponse
$this->notFound();
// 2) not yet synced but exists on realm (and not a gm character)
if ($subject = DB::Characters($this->realmId)->selectRow(
$subjects = DB::Characters($this->realmId)->select(
'SELECT c.`guid` AS "realmGUID", c.`name`, c.`race`, c.`class`, c.`level`, c.`gender`, c.`at_login`, g.`guildid` AS "guildGUID", IFNULL(g.`name`, "") AS "guildName", IFNULL(gm.`rank`, 0) AS "guildRank"
FROM characters c
LEFT JOIN guild_member gm ON gm.`guid` = c.`guid`
LEFT JOIN guild g ON g.`guildid` = gm.`guildid`
WHERE c.`name` = ? AND `level` <= ?d AND (`extra_flags` & ?d) = 0',
Util::ucFirst($this->subjectName), MAX_LEVEL, Profiler::CHAR_GMFLAGS
))
);
if ($subject = array_filter($subjects, fn($x) => Util::lower($x['name']) == Util::lower($this->subjectName)))
{
$subject['realm'] = $this->realmId;
$subject['cuFlags'] = PROFILER_CU_NEEDS_RESYNC;
$subject = $subject[0];
$subject['realm'] = $this->realmId;
$subject['stub'] = 1;
if ($subject['at_login'] & 0x1)
$subject['renameItr'] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ?', $this->realmId, $subject['name']);
$subject['renameItr'] = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ?', $this->realmId, $subject['name']);
if ($subject['guildGUID'])
{
// create empty guild if nessecary to satisfy foreign keys
// create empty guild if necessary to satisfy foreign keys
$subject['guild'] = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_guild WHERE `realm` = ?d AND `realmGUID` = ?d', $this->realmId, $subject['guildGUID']);
if (!$subject['guild'])
$subject['guild'] = DB::Aowow()->query('INSERT INTO ?_profiler_guild (`realm`, `realmGUID`, `cuFlags`, `name`) VALUES (?d, ?d, ?d, ?)', $this->realmId, $subject['guildGUID'], PROFILER_CU_NEEDS_RESYNC, $subject['guildName']);
$subject['guild'] = DB::Aowow()->query('INSERT INTO ?_profiler_guild (`realm`, `realmGUID`, `stub`, `name`, `nameUrl`) VALUES (?d, ?d, 1, ?, ?)', $this->realmId, $subject['guildGUID'], $subject['guildName'], Profiler::urlize($subject['guildName']));
}
unset($subject['guildGUID'], $subject['guildName'], $subject['at_login']);
@@ -122,7 +124,10 @@ class ProfileBaseResponse extends TemplateResponse
protected function generate() : void
{
if ($this->doResync)
{
parent::generate();
return;
}
if ($this->typeId)
{

View File

@@ -38,7 +38,7 @@ class ProfilePowerResponse extends TextResponse implements ICache
if (preg_match('/([^\-]+)-(\d+)/i', $this->subjectName, $m))
[, $this->subjectName, $renameItr] = $m;
if ($x = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND LOWER(`name`) = ? AND `renameItr` = ?d', $this->realmId, $this->subjectName, $renameItr ?? 0))
if ($x = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ? AND `renameItr` = ?d', $this->realmId, Util::ucWords($this->subjectName), $renameItr ?? 0))
$this->typeId = $x;
}

View File

@@ -32,7 +32,7 @@ class ProfileSaveResponse extends TextResponse
'copy' => ['filter' => FILTER_VALIDATE_INT ],
'public' => ['filter' => FILTER_VALIDATE_INT ],
'gearscore' => ['filter' => FILTER_VALIDATE_INT ],
'inv' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned'], 'flags' => FILTER_REQUIRE_ARRAY]
'inv' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdList'], 'flags' => FILTER_REQUIRE_ARRAY]
);
public function __construct(string $pageParam)
@@ -72,7 +72,8 @@ class ProfileSaveResponse extends TextResponse
'glyphs2' => $this->_post['glyphs2'],
'gearscore' => $this->_post['gearscore'],
'icon' => $this->_post['icon'],
'cuFlags' => PROFILER_CU_PROFILE | ($this->_post['public'] ? PROFILER_CU_PUBLISHED : 0)
'custom' => 1,
'cuFlags' => $this->_post['public'] ? PROFILER_CU_PUBLISHED : 0
);
// remnant of a conflict between wotlk generic icons and cata+ auto-generated, char-based icons (see profile=avatar)
@@ -88,7 +89,7 @@ class ProfileSaveResponse extends TextResponse
if ($_ = $this->_post['copy']) // gets set to source profileId when "save as" is clicked. Whats the difference to 'source' though?
{
// get character origin info if possible
if ($r = DB::Aowow()->selectCell('SELECT `realm` FROM ?_profiler_profiles WHERE `id` = ?d AND `realm` IS NOT NULL', $_))
if ($r = DB::Aowow()->selectCell('SELECT `realm` FROM ?_profiler_profiles WHERE `id` = ?d AND `custom` = 0', $_))
$cuProfile['realm'] = $r;
$cuProfile['sourceId'] = $_;
@@ -105,7 +106,7 @@ class ProfileSaveResponse extends TextResponse
}
else // new
{
$nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE `user` = ?d AND (`cuFlags` & ?d) = 0 AND `realmGUID` IS NULL', User::$id, PROFILER_CU_DELETED);
$nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE `user` = ?d AND `deleted` = 0 AND `custom` = 1', User::$id);
if ($nProfiles < 10 || User::isPremium())
if ($newId = DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a)', array_keys($cuProfile), array_values($cuProfile)))
$charId = $newId;
@@ -139,6 +140,12 @@ class ProfileSaveResponse extends TextResponse
{
foreach ($this->_post['inv'] as $slot => $itemData)
{
if (!$itemData)
{
trigger_error('ProfileSaveResponse::generate - skipping malformed inventory definition for slot #'.$slot.': '.Util::toString($itemData), E_USER_NOTICE);
continue;
}
if ($slot + 1 == array_sum($itemData)) // only slot definition set => empty slot
{
DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE `id` = ?d AND `slot` = ?d', $charId, $itemData[0]);

View File

@@ -39,7 +39,7 @@ class ProfileStatusResponse extends TextResponse
if (!$ids)
{
trigger_error('ProfileStatusResponse - no profileIds to resync'.($this->_get['guild'] ? ' for guild #'.$this->_get['guild'] : ($this->_get['arena-team'] ? ' for areana team #'.$this->_get['arena-team'] : '')), E_USER_ERROR);
trigger_error('ProfileStatusResponse - no profileIds to resync'.($this->_get['guild'] ? ' for guild #' : ($this->_get['arena-team'] ? ' for areana team #' : ' #')).Util::toString($this->_get['id']), E_USER_WARNING);
$this->result = Util::toJSON([1, [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_CHAR]]);
}

View File

@@ -57,6 +57,12 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new ProfileListFilter($this->_get['filter'] ?? '', ['realms' => $realms]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -64,8 +70,6 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
{
$this->h1 = Util::ucFirst(Lang::game('profiles'));
$this->filter->evalCriteria();
/*************/
/* Menu Path */
@@ -94,8 +98,6 @@ class ProfilesBaseResponse extends TemplateResponse implements IProfilerList
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
$fiExtraCols = $this->filter->fiExtraCols;
$lvData = [];

View File

@@ -10,7 +10,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'quest';
protected string $pageName = 'quest';
@@ -64,6 +64,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$_flags = $this->subject->getField('flags');
$_specialFlags = $this->subject->getField('specialFlags');
$_side = ChrRace::sideFromMask($this->subject->getField('reqRaceMask'));
$hasCompletion = !($_flags & QUEST_FLAG_UNAVAILABLE || $this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW);
/*************/
@@ -196,7 +197,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
// timer
if ($_ = $this->subject->getField('timeLimit'))
$infobox[] = Lang::quest('timer').Util::formatTime($_ * 1000);
$infobox[] = Lang::quest('timer').DateTime::formatTimeElapsedFloat($_ * 1000);
$startEnd = DB::Aowow()->select('SELECT * FROM ?_quests_startend WHERE `questId` = ?d', $this->typeId);
@@ -271,8 +272,25 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::game('difficulty').implode('[small] &nbsp;[/small]', $_);
}
// id
$infobox[] = Lang::quest('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && $hasCompletion)
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_quests WHERE `questId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
/*******************/
@@ -300,7 +318,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$olItems[$i] = [$id, $qty, $id == $olItems[0][0]];
}
if ($ids = array_column($olItems, 0))
if ($ids = array_filter(array_column($olItems, 0)))
{
$olItemData = new ItemList(array(['id', $ids]));
$this->extendGlobalData($olItemData->getJSGlobals(GLOBALINFO_SELF));
@@ -478,7 +496,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
if ($_ = $this->subject->getField('sourceSpellId'))
{
$this->extendGlobalIds(Type::SPELL, $_);
$this->objectiveList[] = [0, new IconElement(Type::SPELL, $_, SpellList::getName($_), extraText: Lang::quest('provided'), element: 'iconlist-icon')];
$this->objectiveList[] = [0, new IconElement(Type::SPELL, $_, SpellList::getName($_), extraText: Lang::quest('provided'), element: 'iconlist-icon', size: IconElement::SIZE_SMALL)];
}
// required money
@@ -500,8 +518,8 @@ class QuestBaseResponse extends TemplateResponse implements ICache
// todo (med): this double list creation very much sucks ...
$getItemSource = function ($itemId, $method = 0) use (&$mapNPCs, &$mapGOs)
{
$lootTabs = new Loot();
if ($lootTabs->getByItem($itemId))
$lootTabs = new LootByItem($itemId);
if ($lootTabs->getByItem())
{
/*
todo (med): sanity check:
@@ -513,22 +531,22 @@ class QuestBaseResponse extends TemplateResponse implements ICache
for the moment:
if an item has >10 sources, only display sources with >80% chance
always filter sources with <1% chance
always filter sources with <5% chance
*/
$nSources = 0;
foreach ($lootTabs->iterate() as [$type, $data])
if ($type == 'creature' || $type == 'object')
$nSources += count(array_filter($data['data'], function($val) { return $val['percent'] >= 1.0; }));
$nSources += count(array_filter($data['data'], fn($x) => $x['percent'] >= 5.0));
foreach ($lootTabs->iterate() as $idx => [$file, $tabData])
foreach ($lootTabs->iterate() as [$file, $tabData])
{
if (!$tabData['data'])
continue;
foreach ($tabData['data'] as $data)
{
if ($data['percent'] < 1.0)
if ($data['percent'] < 5.0)
continue;
if ($nSources > 10 && $data['percent'] < 80.0)
@@ -961,7 +979,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
// tab: conditions
$cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_QUEST_AVAILABLE, Conditions::SRC_QUEST_SHOW_MARK)
$cnd->getBySource([Conditions::SRC_QUEST_AVAILABLE, Conditions::SRC_QUEST_SHOW_MARK], entry: $this->typeId)
->getByCondition(Type::QUEST, $this->typeId)
->prepare();
@@ -1141,7 +1159,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
'header' => array(
$rmtId,
null,
$delay ? Lang::mail('mailIn', [Util::formatTime($delay * 1000)]) : null,
$delay ? Lang::mail('mailIn', [DateTime::formatTimeElapsed($delay * 1000)]) : null,
)
);
@@ -1157,8 +1175,8 @@ class QuestBaseResponse extends TemplateResponse implements ICache
$this->mail['header'][1] = Lang::mail('mailBy', [$senderTypeId, $ti]);
// while mail attachemnts are handled as loot, it has no variance. Always 100% chance, always one item.
$mailLoot = new Loot();
if ($mailLoot->getByContainer(LOOT_MAIL, $rmtId))
$mailLoot = new LootByContainer();
if ($mailLoot->getByContainer(Loot::MAIL, [$rmtId]))
{
$this->extendGlobalData($mailLoot->jsGlobals);
foreach ($mailLoot->getResult() as $loot)
@@ -1286,7 +1304,7 @@ class QuestBaseResponse extends TemplateResponse implements ICache
['reqQ', array('OR', ['AND', ['nextQuestId', $this->typeId], ['exclusiveGroup', 0, '<']], ['AND', ['id', $this->subject->getField('prevQuestId')], ['nextQuestIdChain', $this->typeId, '!']])],
// Requires one of these quests (Requires one of the quests to choose from)
['reqOneQ', array('OR', ['AND', ['exclusiveGroup', 0, '>'], ['nextQuestId', $this->typeId]], ['breadCrumbForQuestId', $this->typeId])],
['reqOneQ', array('OR', ['AND', ['exclusiveGroup', 0, '>='], ['nextQuestId', $this->typeId]], ['breadCrumbForQuestId', $this->typeId])],
// Opens Quests (Quests that become available only after complete this quest (optionally only one))
['opensQ', array('OR', ['AND', ['prevQuestId', $this->typeId], ['id', $this->subject->getField('nextQuestIdChain'), '!']], ['id', $this->subject->getField('nextQuestId')], ['id', $this->subject->getField('breadcrumbForQuestId')])],

View File

@@ -32,7 +32,7 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
);
protected int $type = Type::QUEST;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'quests';
protected string $pageName = 'quests';
@@ -53,6 +53,12 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new QuestListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -64,13 +70,9 @@ class QuestsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
if (isset($this->category[1]))
$conditions[] = ['zoneOrSort', $this->category[1]];
else if (isset($this->category[0]))

View File

@@ -16,7 +16,7 @@ class RaceBaseResponse extends TemplateResponse implements ICache
[7952, 33554], null, [16264, 33557], [17584, 33657]
);
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'race';
@@ -99,6 +99,13 @@ class RaceBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::race('startZone').Lang::main('colon').'[zone='.$_.']';
}
// id
$infobox[] = Lang::race('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -108,12 +115,14 @@ class RaceBaseResponse extends TemplateResponse implements ICache
/****************/
$this->expansion = Util::$expansionString[$this->subject->getField('expansion')];
$this->headIcons = ['race_'.$ra->json().'_male', 'race_'.$ra->json().'_female'];
$this->redButtons = array(
BUTTON_WOWHEAD => true,
BUTTON_LINKS => ['type' => $this->type, 'typeId' => $this->typeId]
);
if ($_ = $ra->json())
$this->headIcons = ['race_'.$_.'_male', 'race_'.$_.'_female'];
/**************/
/* Extra Tabs */

View File

@@ -11,7 +11,7 @@ class RacesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::CHR_RACE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'races';

View File

@@ -70,7 +70,7 @@ class SearchBaseResponse extends TemplateResponse implements ICache
$this->lvTabs->addListviewTab(new Listview(...$lvData));
// we already have a target > can't have more targets > no redirects
if ($canRedirect && $redirectTo)
if (($canRedirect && $redirectTo) || count($lvData[0]['data']) > 1)
$canRedirect = false;
if ($canRedirect) // note - we are very lucky that in case of searches $template is identical to the typeString

View File

@@ -0,0 +1,30 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class SignatureDeleteResponse extends TextResponse
{
protected bool $requiresLogin = true;
protected array $expectedGET = array(
'id' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']]
);
public function __construct(string $pageParam)
{
parent::__construct($pageParam);
}
protected function generate() : void
{
if (!$this->assertGET('id'))
$this->generate404();
// DB::Aowow()->query(DELETE FROM ?_account_signatures WHERE `id` IN (?a) { AND `accountId` = ?d }', $this->_get['id], User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id);
}
}
?>

View File

@@ -0,0 +1,60 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
// introductory blog post
// https://web.archive.org/web/20210419162936/https://www.wowhead.com/news/new-feature-preview-forum-signatures-175630
// looks like it was .. at best .. live for half a year
// only example seen. looks like archive.org had a hickup when parsing the markup js
// https://web.archive.org/web/20110924014309/http://www.wowhead.com/signature=generate&id='+b.unnamed+'.png
// no clue where generated images are stored.
// static/uploads/signatures/ indicates users can upload their own backgrounds
// unclear when updated. With every char sync?
// generating and also viewing
class SignatureGenerateResponse extends TextResponse
{
protected string $contentType = MIME_TYPE_PNG;
protected array $expectedGET = array(
'id' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkProfileId']]
);
public function __construct(string $pageParam)
{
parent::__construct($pageParam);
}
protected function generate() : void
{
parent::generate();
if (!$this->assertGET('id'))
$this->generate404();
// find file in storage
// build image
}
public function generate404(?string $out = null) : never
{
// "Signature Unavailable"
$out = /*data:image/png;base64,*/'iVBORw0KGgoAAAANSUhEUgAAAdQAAAA8CAIAAABQJdxgAAALbElEQVR4nO3bXWxT5R8H8OectnvtDtB2fVnXN6Cr29zW0SCSjMgSh0YSifPtjgsu1EuvjXFojPfKDRLitYQYxEggEwzhQiJb1+GG4MrWjrbr1petW6FubXee/8UTT05aKBv85az4/VyY9uyc5y32y9PfOeXMZjMBAIBni1d6AAAA/0UIXwAABSB8AQAUgPAFAFAAwhcAQAEIXwAABSB8AQAUgPAFAFAAwhcAQAEIXwAABSB8AQAUgPAFAFAAwhcAQAEIXwAABSB8AQAUgPAFAFAAwhcAQAEIXwAABSB8AQAUgPAFAFAAwhcAQAEIXwAABaiVHgDA8+bzzz+XXouiKH+72TM33hRUHYRv1di7d++BAwcEQZCOcBzHXgwNDR07dsxkMhFCFhYWvvvuO2WG+K8pD52hoaHy4+zgs1e++D6fjxBCKR0bG6t87WPP3HhTUF0QvtVhYGCgr6/P5XJt376dZS6ldH19XaPR+P1+QojJZNq3bx8h5Pfff/+3B/Ppp5+q1WpCiCiKX3zxxb/dHcMyiGFTLjkuP/iMlS/+7Oysw+GQ/nWsIBQKuVyuCmduvCmoLqj5VoeXX37Z4/HodLpCoXDz5s2JiYlMJsMSUMJx3LP5iKrVap/P5/P5eP7Z/f8TCoXYi2AwKD+eSqUIIfPz889sJA8lX/zTp0+zUT3WqVOnFhcXK5yw8aag6mDnWx1UKlVjYyMhZGpqamlpaXx8vK+vz263s2+77Nv3+Pg4IaS2tpb999ChQ93d3RqNpkIiU0ql1ysrK2fPno1EIoSQvr6+ffv2NTU1SX9NpVLnzp2LxWLyb/ocx1WuQrIypfwcSimllOM4Sumff/45MjJy+PBhg8HABlksFkdHR4eHh0VRlLfz7bfffvjhhy6Xi43zxIkT7PhPP/301ltvGQyGeDx+5syZko42NbUPPvigwkQIIb/88kuFC+WLzzoq8ah+5eccP36cvchms8PDwxMTEw9tyul0bmTRYItD+FYZt9vN8/yBAwey2Ww0Go1Go+x4SWXw/fff93g8bre7vr6eyKrDhBC/3y+dHAwGd+7cqVKpKKWRSGRwcPDrr78mhOzfv7+np6epqYntbUVRvHfv3ttvv/3NN98Q2Td99kJqkPzz3b+8TCkdmZ6edrlcPM/n8/l8Pt/e3m42m61WK+sol8tpNJpCoXDlyhX5rOfm5uRvpc3gH3/88eabb7IR/vXXX/KONjs1UlbZsFqtZrNZevvYCyuXZStczlgsFrPZzP5ZSqVSgiAUCoU7d+6UtGO3248ePbqRRYMtDmWH6rC2tra0tEQIqaur6+jo8Hq9XV1dPM9ns1m2DZydnSWykHW5XB0dHQ0NDYlEYnx8nH23LRQKLBzZV3iO4/R6/cTERCKR4HneZrPt2LFD6lEQhJs3b/r9/mg0qlKpHA6HTqdjf5Kqq36/X96ghFUG5Ikv9dja2nr37l1KqVqt1mg0ZrPZZrMVCoVAIJBOpxsbG9va2l566aUNLkuhUJBer6+vP+XUQqEQ2y+zIkYsFkun05RSaXaPurBk8R+lwpISQkRRHBsbm5qaopQajUar1frqq6+WN/L6668/5aLBFoHwrQ6XL18OhUKRSCSfzxNC1Gq1wWDwer1Wq3VwcLC8MshxHKsIz83NpdNplg5qtZpSKq8zhsNhlgVEVrXkeZ7juNHRUVEUa2trGxoa5H8teaJgaGjo5MmT8sLliRMnVlZW5OfIe7x161Y6nb5x48bIyAjP8y0tLRzHsdSLxWKEEK1Wy768P4GnmdrJkyeTyWQulyOEbN++XRTFtbU1jUaTy+VSqdTp06cfdeFGyrKVl5RZWFg4f/78vXv3WKmhublZr9eXN2WxWP6/iwZKQdmhOoyMjKRSqf7+fpvNVlNTY7Va9Xo9x3Eul2t5ebm8MkgpLRaLGo3G4XAQQux2OyFkfX1d+rhKzp8/39vbS2QbtzfeeINtpurr61Uq1YMHD0jFbV08Hpe/LU+ikh6/+uor9uL48eMqlYoQ0t7evsF1YFQqFdvnajSaCh1tamrxeDwQCAiC4PF4amtrWblGq9Xevn07EAj09PQ86sKHlmVLbHBJA4FAJpM5duyY0+lkeV3eFMdxT7ZosNUgfKuDdCvp3LlzGo1mYGDAaDTu3r2bfQ7LhcPhuro6t9ut0+l0Oh2ldHV1NRgMsi2wnPzGFLNnz56Ojo76+vr79+/fuXOH4zh5MfSxnuARiEAgIL9ZVD4k6TjHcTzP2+12Vgpoa2tjVd1HnV9ypPLUrl275vP58vl8bW2tw+G4f/9+oVDIZrNXr179+OOPn2ZNNr6klW+Qym1w0WDLQtmhavh8vtbW1iNHjgiCkEgkWP2B2bZtm/xMs9l85syZtbU1tVodDAb9fv/Y2Njk5OTq6qooijabTX4ye4RAYjAYeJ6vq6sjhNy9ezeTycjDVKvVloyK7ayJ7MP/0UcflTRYMjx5+ZXFhyAI8Xjc7/enUqlkMnnr1q3y6VNK2cl6vf7dd9/1eDzd3d1HjhwxmUyiKLLeSzra7NSWl5dnZmbYzT2tVms0GqPR6Ozs7MrKSoULLRaLvBez2VwyDKPRWOFyuffee+/w4cOs2sC29uVNbWrRYCtTlX+cYAvq7+9vaWlpbGzUarU6nW7btm0ej0elUi0uLs7Nzb3yyiuNjY0Gg4EQ8vfff3d1dV2+fPngwYOsOmGxWFihsLm5uaampqurSzo5l8u1t7c3NDQ0NzcTQh48eOD1egkhrKpICBFF0ePxqNVqjuOWlpYcDsfo6Gh/f7/FYuE4rlgsut1uu92+Y8cOdkSlUjU3NzudTlZxZg3u379f6jGbze7Zs+fq1auEkM7OzsbGxqamJkEQisWixWLp7e3duXPn7du3y6PkxRdfrKurEwRBEASVSrVr167Ozk6bzWY2m1lE3rhx45NPPnnKqaVSqY6ODpPJxPO8KIrT09M//PBDNps9ePDgoy70er0liy+fby6X6+7urtBvsVhsaWmhlOp0Oo7jDAaD0+kkhMzNzc3MzAwMDJQ0lUwmN75osJVh51tlBEHo6urq6elRq9WJRCIcDrMHjF544QV2wq5duwghR48eZeFI/rmxw3FcTU2N2+2Wn7x7924iqx6yv+ZyuWg0Sik1mUzd3d2ZTGZ1dZVSKl21urrKHgOw2Wws0TKZDLvEaDRaLJZYLMa2oqxBeY8ej0eay/DwcDwen5+f53m+s7Ozp6eH5/nJycmHTvznn3+en5+fmZkpFAqtra29vb1er9dgMLD7kBcuXCjp6Mmmxp5wYGXrpaWlTCbDNsKVLyxZ/PJhVL6cUppOp/P5fG9vb1tbGyEkHo8vLCxcunSpvKlNLRpsZaj5Vg32UJe8tLe8vHzlyhX2rGvJj2udTicL6Onp6UwmQwjR6XTsAVtS9kvckrdnz54dHBxcWFhgb/P5fCwWk3ZthJCLFy/yPB8Oh6Xx/Pjjj++8804ikZAaKXni9aG//Q0Gg99///1rr70m3SgTRXFycvLixYvlJ4fD4VOnTg0MDCSTSemnfZTSaDR66dIlqYWnnBoh5Ndff9Xr9ewxiWvXrj32Qo7jyme3qX6vX7/OKtfSvFZWVi5cuMDq2iVNbWrRYCvjpMfI4Xny2Wef7d27l+O4ZDIZiUQ4jrPZbAaDYXFxcWpq6ssvv1R6gAD/ddj5Pp8KhUIkEmlpaTEYDKxiSCnNZDKhUGh4eFjp0QEAdr7PKbvdzn4KJb+xns1mf/vtt+vXrys4MABgEL4AAArA0w4AAApA+AIAKADhCwCgAIQvAIACEL4AAApA+AIAKADhCwCgAIQvAIACEL4AAApA+AIAKADhCwCgAIQvAIACEL4AAApA+AIAKADhCwCgAIQvAIACEL4AAApA+AIAKADhCwCggP8BQFB72fG1SEAAAAAASUVORK5CYII=';
parent::generate404(base64_decode($out));
}
protected static function checkProfileId(string $sigId) : ?int
{
if (preg_match('/^(\d+)\.png$/i', $sigId, $m))
return $m[0];
return null;
}
}
?>

View File

@@ -0,0 +1,55 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class SignatureBaseResponse extends TemplateResponse
{
protected bool $requiresLogin = true;
protected string $template = 'text-page-generic';
protected string $pageName = 'signature';
protected array $expectedGET = array(
'profile' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkProfile']] // optional - full profile string to build sig from
);
private int $id = 0;
public function __construct(string $pageParam)
{
parent::__construct($pageParam);
if ($pageParam)
$this->id = intVal($pageParam);
else if ($this->assertGET('profile'))
$this->id = $this->_get['profile'];
else
$this->generateError();
}
protected function generate() : void
{
// show editor
parent::generate();
}
protected static function checkProfile(string $profile) : ?int
{
if (!preg_match('/^([a-z]+)\.([a-z_]+)\.(.+)$/i', $profile, $m))
return null;
[, $region, $realm, $char] = $m;
$realms = Profiler::getRealms();
if ($rId = array_find_key($realms, fn($x) => $x['region'] == $region && $x['name'] == $realm))
return DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `custom` = 0 AND `name` = ?', $rId, urldecode($char)) ?: null;
return null;
}
}
?>

View File

@@ -10,7 +10,7 @@ class SkillBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'skill';
@@ -70,6 +70,9 @@ class SkillBaseResponse extends TemplateResponse implements ICache
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
// id
$infobox[] = Lang::skill('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
@@ -77,6 +80,10 @@ class SkillBaseResponse extends TemplateResponse implements ICache
$this->extendGlobalIds(Type::ICON, $_);
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
@@ -220,6 +227,26 @@ class SkillBaseResponse extends TemplateResponse implements ICache
}
}
// tab: modified by [spell]
$conditions = array(
'OR',
['AND', ['effect1AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect1MiscValue', $this->typeId]],
['AND', ['effect2AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect2MiscValue', $this->typeId]],
['AND', ['effect3AuraId', [SPELL_AURA_MOD_SKILL, SPELL_AURA_MOD_SKILL_TALENT]], ['effect3MiscValue', $this->typeId]]
);
$modBy = new SpellList($conditions);
if (!$modBy->error)
{
$this->extendGlobalData($modBy->getJSGlobals());
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $modBy->getListviewData(),
'id' => 'modified-by',
'name' => '$LANG.tab_modifiedby',
'hiddenCols' => ['skill'],
), SpellList::$brickFile));
}
// tab: spells [spells] (exclude first tab)
$reqClass = 0x0;
$reqRace = 0x0;

View File

@@ -11,7 +11,7 @@ class SkillsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::SKILL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'skills';

View File

@@ -10,7 +10,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'sound';
protected string $pageName = 'sound';
@@ -212,7 +212,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
}
}
if ($worldStates = array_filter($zoneIds, function ($x) { return $x['worldStateId'] > 0; }))
if ($worldStates = array_filter($zoneIds, fn($x) => $x['worldStateId'] > 0))
{
$tabData['extraCols'] = ['$Listview.extraCols.condition'];
@@ -222,7 +222,7 @@ class SoundBaseResponse extends TemplateResponse implements ICache
Conditions::extendListviewRow($zoneData[$state['id']], Conditions::SRC_NONE, $this->typeId, [Conditions::WORLD_STATE, $state['worldStateId'], $state['worldStateValue']]);
else
foreach ($zoneData as &$d)
if (in_array($state['id'], $d['subzones']))
if (in_array($state['id'], $d['subzones'] ?? []))
Conditions::extendListviewRow($d, Conditions::SRC_NONE, $this->typeId, [Conditions::WORLD_STATE, $state['worldStateId'], $state['worldStateValue']]);
}
}

View File

@@ -11,7 +11,7 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::SOUND;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'sounds';
protected string $pageName = 'sounds';
@@ -34,6 +34,12 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new SoundListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -41,18 +47,13 @@ class SoundsBaseResponse extends TemplateResponse implements ICache
{
$this->h1 = Util::ucFirst(Lang::game('sounds'));
$this->filter->evalCriteria();
$conditions = [];
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/**************/
/* Page Title */

View File

@@ -14,9 +14,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
SPELL_AURA_ABILITY_PERIODIC_CRIT, SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL, SPELL_AURA_ABILITY_IGNORE_AURASTATE,
SPELL_AURA_ALLOW_ONLY_ABILITY, SPELL_AURA_IGNORE_MELEE_RESET, SPELL_AURA_ABILITY_CONSUME_NO_AMMO,
SPELL_AURA_MOD_IGNORE_SHAPESHIFT, SPELL_AURA_PERIODIC_HASTE, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS,
SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELL_AURA_ADD_TARGET_TRIGGER, /* SPELL_AURA_DUMMY ? */];
SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELL_AURA_ADD_TARGET_TRIGGER, SPELL_AURA_IGNORE_COMBAT_RESULT, /* SPELL_AURA_DUMMY ? */];
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'spell';
protected string $pageName = 'spell';
@@ -26,7 +26,6 @@ class SpellBaseResponse extends TemplateResponse implements ICache
public int $type = Type::SPELL;
public int $typeId = 0;
public array $reagents = [false, null];
public array $scaling = [];
public string $items = '';
public array $tools = [];
public array $effects = [];
@@ -50,6 +49,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
private int $firstRank = 0;
private array $modelInfo = [];
private array $difficulties = [];
private int $mapType = 0;
public function __construct(string $id)
{
@@ -68,15 +68,20 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($jsg = $this->subject->getJSGlobals(GLOBALINFO_ANY, $extra))
$this->extendGlobalData($jsg, $extra);
$this->modelInfo = $this->subject->getModelInfo($this->typeId);
$this->difficulties = DB::Aowow()->selectRow( // has difficulty versions of itself
$this->modelInfo = $this->subject->getModelInfo($this->typeId);
if ($spelldifficulty = DB::Aowow()->select( // has difficulty versions of itself
'SELECT `normal10` AS "0", `normal25` AS "1",
`heroic10` AS "2", `heroic25` AS "3"
`heroic10` AS "2", `heroic25` AS "3",
`mapType` AS ARRAY_KEY
FROM ?_spelldifficulty
WHERE `normal10` = ?d OR `normal25` = ?d OR
`heroic10` = ?d OR `heroic25` = ?d',
$this->typeId, $this->typeId, $this->typeId, $this->typeId
);
))
{
$this->mapType = key($spelldifficulty);
$this->difficulties = array_pop($spelldifficulty);
}
// returns self or firstRank
if ($fr = DB::World()->selectCell('SELECT `first_spell_id` FROM spell_ranks WHERE `spell_id` = ?d', $this->typeId))
@@ -161,13 +166,6 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->createReagentList();
/**********************/
/* Spell Scaling Info */
/**********************/
$this->createScalingData();
/******************/
/* Required Items */
/******************/
@@ -236,7 +234,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->castTime = $this->subject->createCastTimeForCurrent(false, false);
$this->level = $this->subject->getField('spellLevel');
$this->rangeName = $this->subject->getField('rangeText', true);
$this->gcd = Util::formatTime($this->subject->getField('startRecoveryTime'));
$this->gcd = DateTime::formatTimeElapsedFloat($this->subject->getField('startRecoveryTime'));
$this->school = $this->fmtStaffTip(Lang::getMagicSchools($this->subject->getField('schoolMask')), Util::asHex($this->subject->getField('schoolMask')));
$this->dispel = $this->subject->getField('dispelType') ? Lang::game('dt', $this->subject->getField('dispelType')) : null;
$this->mechanic = $this->subject->getField('mechanic') ? Lang::game('me', $this->subject->getField('mechanic')) : null;
@@ -266,12 +264,12 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->stances = Lang::getStances($this->subject->getField('stanceMask'));
if (($_ = $this->subject->getField('recoveryTime')) && $_ > 0)
$this->cooldown = Util::formatTime($_);
$this->cooldown = DateTime::formatTimeElapsedFloat($_);
else if (($_ = $this->subject->getField('recoveryCategory')) && $_ > 0)
$this->cooldown = Util::formatTime($_);
$this->cooldown = DateTime::formatTimeElapsedFloat($_);
if (($_ = $this->subject->getField('duration')) && $_ > 0)
$this->duration = Util::formatTime($_);
$this->duration = DateTime::formatTimeElapsedFloat($_);
/**************/
@@ -449,43 +447,32 @@ class SpellBaseResponse extends TemplateResponse implements ICache
['s.effect1Id', $this->subject->getField('effect1Id')],
['s.effect2Id', $this->subject->getField('effect2Id')],
['s.effect3Id', $this->subject->getField('effect3Id')],
['s.id', $this->subject->id, '!'],
['s.id', $this->typeId, '!'],
['s.name_loc'.Lang::getLocale()->value, $this->subject->getField('name', true)]
);
if ($this->difficulties)
$conditions = ['OR', ['AND', ...$conditions], ['AND', ['s.id', $this->difficulties], ['s.id', $this->typeId, '!']]];
$saSpells = new SpellList($conditions);
if (!$saSpells->error)
{
$data = $saSpells->getListviewData();
if ($this->difficulties) // needs a way to distinguish between dungeon and raid :x; creature using this -> map -> areaType?
if ($this->difficulties)
{
$saE = ['$Listview.extraCols.mode'];
foreach ($data as $id => &$d)
{
$d['modes'] = ['mode' => 0];
if ($this->difficulties[0] == $id) // b0001000
if (($modeBit = array_search($id, $this->difficulties)) !== false)
{
if (!$this->difficulties[2] && !$this->difficulties[3])
$d['modes']['mode'] |= 0x2;
if ($this->mapType)
$d['modes'] = ['mode' => 1 << ($modeBit + 3)];
else
$d['modes']['mode'] |= 0x8;
$d['modes'] = ['mode' => 2 - $modeBit];
}
if ($this->difficulties[1] == $id) // b0010000
{
if (!$this->difficulties[2] && !$this->difficulties[3])
$d['modes']['mode'] |= 0x1;
else
$d['modes']['mode'] |= 0x10;
}
if ($this->difficulties[2] == $id) // b0100000
$d['modes']['mode'] |= 0x20;
if ($this->difficulties[3] == $id) // b1000000
$d['modes']['mode'] |= 0x40;
else
$d['modes'] = ['mode' => 0];
}
}
@@ -534,7 +521,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: used by - spell
if ($so = DB::Aowow()->selectCell('SELECT `id` FROM ?_spelloverride WHERE `spellId1` = ?d OR `spellId2` = ?d OR `spellId3` = ?d OR `spellId4` = ?d OR `spellId5` = ?d', $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id, $this->subject->id))
if ($so = DB::Aowow()->selectCell('SELECT `id` FROM ?_spelloverride WHERE `spellId1` = ?d OR `spellId2` = ?d OR `spellId3` = ?d OR `spellId4` = ?d OR `spellId5` = ?d', $this->typeId, $this->typeId, $this->typeId, $this->typeId, $this->typeId))
{
$conditions = array(
'OR',
@@ -559,8 +546,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - itemset
$conditions = array(
'OR',
['spell1', $this->subject->id], ['spell2', $this->subject->id], ['spell3', $this->subject->id], ['spell4', $this->subject->id],
['spell5', $this->subject->id], ['spell6', $this->subject->id], ['spell7', $this->subject->id], ['spell8', $this->subject->id]
['spell1', $this->typeId], ['spell2', $this->typeId], ['spell3', $this->typeId], ['spell4', $this->typeId],
['spell5', $this->typeId], ['spell6', $this->typeId], ['spell7', $this->typeId], ['spell8', $this->typeId]
);
$ubSets = new ItemsetList($conditions);
@@ -578,11 +565,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - item
$conditions = array(
'OR',
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN, '!'], ['spellId1', $this->subject->id]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN, '!'], ['spellId2', $this->subject->id]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN, '!'], ['spellId3', $this->subject->id]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN, '!'], ['spellId4', $this->subject->id]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN, '!'], ['spellId5', $this->subject->id]]
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN, '!'], ['spellId1', $this->typeId]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN, '!'], ['spellId2', $this->typeId]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN, '!'], ['spellId3', $this->typeId]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN, '!'], ['spellId4', $this->typeId]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN, '!'], ['spellId5', $this->typeId]]
);
$ubItems = new ItemList($conditions);
@@ -600,8 +587,8 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: used by - object
$conditions = array(
'OR',
['onUseSpell', $this->subject->id], ['onSuccessSpell', $this->subject->id],
['auraSpell', $this->subject->id], ['triggeredSpell', $this->subject->id]
['onUseSpell', $this->typeId], ['onSuccessSpell', $this->typeId],
['auraSpell', $this->typeId], ['triggeredSpell', $this->typeId]
);
if (!empty($ubSAI[Type::OBJECT]))
$conditions[] = ['id', $ubSAI[Type::OBJECT]];
@@ -656,54 +643,54 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: contains
// spell_loot_template & skill_extra_item_template
$extraItem = DB::World()->selectRow('SELECT * FROM skill_extra_item_template WHERE `spellid` = ?d', $this->subject->id);
$spellLoot = new Loot();
if ($spellLoot->getByContainer(LOOT_SPELL, $this->subject->id) || $extraItem)
// spell_loot_template
$spellLoot = new LootByContainer();
if ($spellLoot->getByContainer(Loot::SPELL, [$this->typeId]))
{
$this->extendGlobalData($spellLoot->jsGlobals);
$lv = $spellLoot->getResult();
$extraCols = $spellLoot->extraCols;
$extraCols[] = '$Listview.extraCols.percent';
$lvName = '$LANG.tab_contains';
if ($extraItem && $this->subject->canCreateItem())
{
$foo = $this->subject->relItems->getListviewData();
for ($i = 1; $i < 4; $i++)
{
if (($bar = $this->subject->getField('effect'.$i.'CreateItemId')) && isset($foo[$bar]))
{
$lvName = '$LANG.tab_bonusloot';
$lv[$bar] = $foo[$bar];
$lv[$bar]['percent'] = $extraItem['additionalCreateChance'];
$lv[$bar]['pctstack'] = $this->buildPctStack($extraItem['additionalCreateChance'] / 100, $extraItem['additionalMaxNum']);
if ($max = ($extraItem['additionalMaxNum'] - 1))
$lv[$bar]['stack'] = [1, $max];
if (Conditions::extendListviewRow($lv[$bar], Conditions::SRC_NONE, $this->typeId, [Conditions::SPELL, $extraItem['requiredSpecialization']]))
{
$this->extendGlobalIds(Type::SPELL, $extraItem['requiredSpecialization']);
$extraCols[] = '$Listview.extraCols.condition';
}
break; // skill_extra_item_template can only contain 1 item
}
}
}
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lv,
'name' => $lvName,
'id' => 'contains',
'hiddenCols' => ['side', 'slot', 'source', 'reqlevel'],
'extraCols' => array_unique($extraCols)
'data' => $spellLoot->getResult(),
'name' => '$LANG.tab_contains',
'id' => 'contains',
'hiddenCols' => ['side', 'slot', 'source', 'reqlevel'],
'extraCols' => array_unique($extraCols),
'computeDataFunc' => '$Listview.funcBox.initLootTable'
), ItemList::$brickFile));
}
// tab: bonus loot
if ($extraItemData = DB::World()->select('SELECT `spellId` AS ARRAY_KEY, `additionalCreateChance` AS "0", `additionalMaxNum` AS "1" FROM skill_extra_item_template WHERE `requiredSpecialization` = ?d', $this->typeId))
{
$extraSpells = new SpellList(array(['id', array_keys($extraItemData)]));
if (!$extraSpells->error)
{
$this->extendGlobalData($extraSpells->getJSGlobals(GLOBALINFO_RELATED));
$lvItems = $extraSpells->getListviewData();
foreach ($lvItems as $iId => $data)
{
[$chance, $maxItr] = $extraItemData[$iId];
$lvItems[$iId]['count'] = 1; // expected by js or the pct-col becomes unsortable
$lvItems[$iId]['percent'] = $chance;
$lvItems[$iId]['pctstack'] = $this->buildPctStack($chance / 100, $maxItr, $data['creates'][1]);
$lvItems[$iId]['creates'][2] *= $maxItr;
}
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lvItems,
'name' => '$LANG.tab_bonusloot',
'id' => 'bonusloot',
'hiddenCols' => ['side', 'reqlevel'],
'extraCols' => ['$Listview.extraCols.percent']
), SpellList::$brickFile));
}
}
// tab: exclusive with
if ($this->firstRank && DB::World()->selectCell('SELECT 1 FROM spell_group WHERE `spell_id` = ?d', $this->firstRank))
{
@@ -819,9 +806,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: triggered by
$conditions = array(
'OR',
['AND', ['OR', ['effect1Id', SpellList::EFFECTS_TRIGGER], ['effect1AuraId', SpellList::AURAS_TRIGGER]], ['effect1TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect2Id', SpellList::EFFECTS_TRIGGER], ['effect2AuraId', SpellList::AURAS_TRIGGER]], ['effect2TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect3Id', SpellList::EFFECTS_TRIGGER], ['effect3AuraId', SpellList::AURAS_TRIGGER]], ['effect3TriggerSpell', $this->subject->id]],
['AND', ['OR', ['effect1Id', SpellList::EFFECTS_TRIGGER], ['effect1AuraId', SpellList::AURAS_TRIGGER]], ['effect1TriggerSpell', $this->typeId]],
['AND', ['OR', ['effect2Id', SpellList::EFFECTS_TRIGGER], ['effect2AuraId', SpellList::AURAS_TRIGGER]], ['effect2TriggerSpell', $this->typeId]],
['AND', ['OR', ['effect3Id', SpellList::EFFECTS_TRIGGER], ['effect3AuraId', SpellList::AURAS_TRIGGER]], ['effect3TriggerSpell', $this->typeId]],
);
$trigger = new SpellList($conditions);
@@ -848,6 +835,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($auras = DB::World()->selectCol('SELECT `entry` FROM creature_template_addon WHERE `auras` REGEXP ?', '\\b'.$this->typeId.'\\b'))
$conditions[] = ['id', $auras];
if ($spellClick = DB::World()->selectCol('SELECT `npc_entry` FROM npc_spellclick_spells WHERE `spell_id` = ?d', $this->typeId))
$conditions[] = ['id', $spellClick];
$ubCreature = new CreatureList($conditions);
if (!$ubCreature->error)
{
@@ -1000,7 +990,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: taught by npc
if ($this->subject->getSources($s) && in_array(SRC_TRAINER, $s))
if ($this->subject->getRawSource(SRC_TRAINER))
{
$trainers = DB::World()->select(
'SELECT cdt.`CreatureId` AS ARRAY_KEY, ts.`ReqSkillLine` AS "reqSkillId", ts.`ReqSkillRank` AS "reqSkillValue", ts.`ReqLevel` AS "reqLevel", ts.`ReqAbility1` AS "reqSpellId1", ts.`reqAbility2` AS "reqSpellId2"
@@ -1057,15 +1047,16 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: taught by spell
$conditions = array(
'OR',
['AND', ['effect1Id', SpellList::EFFECTS_TEACH], ['effect1TriggerSpell', $this->subject->id]],
['AND', ['effect2Id', SpellList::EFFECTS_TEACH], ['effect2TriggerSpell', $this->subject->id]],
['AND', ['effect3Id', SpellList::EFFECTS_TEACH], ['effect3TriggerSpell', $this->subject->id]],
['AND', ['effect1Id', SpellList::EFFECTS_TEACH], ['effect1TriggerSpell', $this->typeId]],
['AND', ['effect2Id', SpellList::EFFECTS_TEACH], ['effect2TriggerSpell', $this->typeId]],
['AND', ['effect3Id', SpellList::EFFECTS_TEACH], ['effect3TriggerSpell', $this->typeId]],
);
$tbSpell = new SpellList($conditions);
$tbsData = [];
if (!$tbSpell->error)
{
$tbsData = $tbSpell->getFoundIDs();
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $tbSpell->getListviewData(),
'id' => 'taught-by-spell',
@@ -1076,15 +1067,14 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// tab: taught by quest
$conditions = ['OR', ['sourceSpellId', $this->typeId], ['rewardSpell', $this->typeId]];
$conditions = array(
'OR',
['sourceSpellId', $this->typeId],
['rewardSpell', $this->typeId],
['rewardSpellCast', $this->typeId]
);
if ($tbsData)
{
$conditions[] = ['rewardSpell', array_keys($tbsData)];
if (User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = ['rewardSpellCast', array_keys($tbsData)];
}
if (User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = ['rewardSpellCast', $this->typeId];
array_push($conditions, ['rewardSpell', $tbsData], ['rewardSpellCast', $tbsData]);
$tbQuest = new QuestList($conditions);
if (!$tbQuest->error)
@@ -1101,11 +1091,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: taught by item (i'd like to precheck $this->subject->sources, but there is no source:item only complicated crap like "drop" and "vendor")
$conditions = array(
'OR',
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN], ['spellId1', $this->subject->id]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN], ['spellId2', $this->subject->id]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN], ['spellId3', $this->subject->id]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN], ['spellId4', $this->subject->id]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN], ['spellId5', $this->subject->id]],
['AND', ['spellTrigger1', SPELL_TRIGGER_LEARN], ['spellId1', $this->typeId]],
['AND', ['spellTrigger2', SPELL_TRIGGER_LEARN], ['spellId2', $this->typeId]],
['AND', ['spellTrigger3', SPELL_TRIGGER_LEARN], ['spellId3', $this->typeId]],
['AND', ['spellTrigger4', SPELL_TRIGGER_LEARN], ['spellId4', $this->typeId]],
['AND', ['spellTrigger5', SPELL_TRIGGER_LEARN], ['spellId5', $this->typeId]],
);
$tbItem = new ItemList($conditions);
@@ -1166,6 +1156,55 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
}
// tab: unlocks (object or item)
$lockIds = DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_SPELL, $this->typeId, LOCK_TYPE_SPELL, $this->typeId,
LOCK_TYPE_SPELL, $this->typeId, LOCK_TYPE_SPELL, $this->typeId,
LOCK_TYPE_SPELL, $this->typeId
);
// we know this spell effect is only in use on index 1
if ($this->subject->getField('effect1Id') == SPELL_EFFECT_OPEN_LOCK && ($lockId = $this->subject->getField('effect1MiscValue')))
$lockIds += DB::Aowow()->selectCol(
'SELECT `id` FROM ?_lock WHERE (`type1` = ?d AND `properties1` = ?d) OR
(`type2` = ?d AND `properties2` = ?d) OR (`type3` = ?d AND `properties3` = ?d) OR
(`type4` = ?d AND `properties4` = ?d) OR (`type5` = ?d AND `properties5` = ?d)',
LOCK_TYPE_SKILL, $lockId, LOCK_TYPE_SKILL, $lockId,
LOCK_TYPE_SKILL, $lockId, LOCK_TYPE_SKILL, $lockId,
LOCK_TYPE_SKILL, $lockId
);
if ($lockIds)
{
// objects
$lockedObj = new GameObjectList(array(Cfg::get('SQL_LIMIT_NONE'), ['lockId', $lockIds]));
if (!$lockedObj->error)
{
$this->addDataLoader('zones');
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lockedObj->getListviewData(),
'name' => '$LANG.tab_unlocks',
'id' => 'unlocks-object',
'visibleCols' => $lockedObj->hasSetFields('reqSkill') ? ['skill'] : null
), GameObjectList::$brickFile));
}
$lockedItm = new ItemList(array(Cfg::get('SQL_LIMIT_NONE'), ['lockId', $lockIds]));
if (!$lockedItm->error)
{
$this->extendGlobalData($lockedItm->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $lockedItm->getListviewData(),
'name' => '$LANG.tab_unlocks',
'id' => 'unlocks-item'
), ItemList::$brickFile));
}
}
// find associated NPC, Item and merge results
// taughtbypets (unused..?)
// taughtbyquest (usually the spell casted as quest reward teaches something; exclude those seplls from taughtBySpell)
@@ -1174,7 +1213,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// tab: conditions
$cnd = new Conditions();
$cnd->getBySourceEntry($this->typeId, Conditions::SRC_SPELL_IMPLICIT_TARGET, Conditions::SRC_SPELL, Conditions::SRC_SPELL_CLICK_EVENT, Conditions::SRC_VEHICLE_SPELL, Conditions::SRC_SPELL_PROC)
$cnd->getBySource([Conditions::SRC_SPELL_IMPLICIT_TARGET, Conditions::SRC_SPELL, Conditions::SRC_SPELL_CLICK_EVENT, Conditions::SRC_VEHICLE_SPELL, Conditions::SRC_SPELL_PROC], entry: $this->typeId)
->getByCondition(Type::SPELL, $this->typeId)
->prepare();
if ($tab = $cnd->toListviewTab())
@@ -1191,7 +1230,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
/* SpellLoot recursive dropchance builder */
/******************************************/
private function buildPctStack(float $baseChance, int $maxStack) : string
private function buildPctStack(float $baseChance, int $maxStack, int $baseCount = 1) : string
{
// note: pctStack does not contain absolute values but chances relative to the overall drop chance
// e.g.: dropChance is 17% then [1 => 50, 2 => 25, 3 => 25] displays > Stack of 1: 8%; Stack of 2: 4%; Stack of 3: 4%
@@ -1212,6 +1251,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
// cleanup tiny fractions
$pctStack = array_filter($pctStack, fn($x) => ($x * $baseChance) >= 0.01);
if ($baseCount > 1)
$pctStack = array_combine(array_map(fn($x) => $x * $baseCount, array_keys($pctStack)), $pctStack);
return json_encode($pctStack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON !
}
@@ -1333,10 +1375,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (!$reagents)
return;
foreach ($this->subject->relItems->iterate() as $iId => $__)
foreach ($reagents as [$iId, $num])
{
if (!in_array($iId, array_keys($reagents)))
continue;
$relItem = $this->subject->relItems->getEntry($iId);
$data = array(
'path' => Type::ITEM.'-'.$iId, // id of the html-element
@@ -1345,11 +1386,12 @@ class SpellBaseResponse extends TemplateResponse implements ICache
'typeStr' => Type::getFileString(Type::ITEM),
'icon' => new IconElement(
Type::ITEM,
$iId,
$this->subject->relItems->getField('name', true),
$reagents[$iId][1],
quality: $this->subject->relItems->getField('quality'),
is_null($relItem) ? 0 : $iId,
is_null($relItem) ? 'Item #'.$iId : $this->subject->relItems->getField('name', true),
$num,
quality: $relItem['quality'] ?? 'q',
size: IconElement::SIZE_SMALL,
link: !is_null($relItem),
align: 'right',
element: 'iconlist-icon'
)
@@ -1372,9 +1414,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->reagents = [$enhanced, $reagentResult];
}
private function createScalingData() : void // calculation mostly like seen in TC
private function calculateEffectScaling() : array // calculation mostly like seen in TC
{
$scaling = ['directSP' => 0, 'dotSP' => 0, 'directAP' => 0, 'dotAP' => 0];
if ($this->subject->getField('attributes3') & SPELL_ATTR3_NO_DONE_BONUS)
return [0, 0, 0, 0];
if (!$this->subject->isScalableDamagingSpell() && !$this->subject->isScalableHealingSpell())
return [0, 0, 0, 0];
$scaling = [0, 0, 0, 0];
$pMask = $this->subject->periodicEffectsMask();
$allDoTs = true;
@@ -1385,25 +1433,20 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($pMask & 1 << ($i - 1))
{
$scaling['dotSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier');
$scaling[1] = $this->subject->getField('effect'.$i.'BonusMultiplier');
continue;
}
else
$scaling['directSP'] = $this->subject->getField('effect'.$i.'BonusMultiplier');
else if ($this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_MAGIC)
$scaling[0] = $this->subject->getField('effect'.$i.'BonusMultiplier');
$allDoTs = false;
}
if ($s = DB::World()->selectRow('SELECT `direct_bonus` AS "directSP", `dot_bonus` AS "dotSP", `ap_bonus` AS "directAP", `ap_dot_bonus` AS "dotAP" FROM spell_bonus_data WHERE `entry` = ?d', $this->firstRank))
if ($s = DB::World()->selectRow('SELECT `direct_bonus` AS "0", `dot_bonus` AS "1", `ap_bonus` AS "2", `ap_dot_bonus` AS "3" FROM spell_bonus_data WHERE `entry` = ?d', $this->firstRank))
$scaling = $s;
if ((!$this->subject->isDamagingSpell() && !$this->subject->isHealingSpell()) ||
!in_array($this->subject->getField('typeCat'), [-2, -3, -7, 7]) ||
$this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_NONE)
{
$this->scaling = array_filter($scaling, fn($x) => $x > 0);
return;
}
if (!in_array($this->subject->getField('typeCat'), [-2, -3, -7, 7]) || $this->subject->getField('damageClass') == SPELL_DAMAGE_CLASS_NONE)
return array_map(fn($x) => $x < 0 ? 0 : $x, $scaling);
foreach ($scaling as $k => $v)
{
@@ -1412,15 +1455,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
continue;
// no known calculation for physical abilities
if ($k == 'directAP' || $k == 'dotAP')
if (in_array($k, [2, 3])) // [direct AP, DoT AP]
continue;
// dont use spellPower to scale physical Abilities
if ($this->subject->getField('schoolMask') == (1 << SPELL_SCHOOL_NORMAL) && ($k == 'directSP' || $k == 'dotSP'))
if ($this->subject->getField('schoolMask') == (1 << SPELL_SCHOOL_NORMAL) && in_array($k, [0, 1]))
continue;
$isDOT = false;
if ($k == 'dotSP' || $k == 'dotAP')
if (in_array($k, [1, 3])) // [DoT SP, DoT AP]
{
if ($pMask)
$isDOT = true;
@@ -1458,7 +1501,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
}
if ($this->subject->isHealingSpell())
if ($this->subject->isScalableHealingSpell())
$castingTime *= 1.88;
// SPELL_SCHOOL_MASK_NORMAL
@@ -1468,7 +1511,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$scaling[$k] = 0; // would be 1 ($dotFactor), but we dont want it to be displayed
}
$this->scaling = array_filter($scaling, fn($x) => $x > 0);
return array_map(fn($x) => $x < 0 ? 0 : $x, $scaling);
}
private function createRequiredItems() : void
@@ -1530,6 +1573,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$spellIdx = array_unique(array_merge($this->subject->canTriggerSpell(), $this->subject->canTeachSpell()));
$itemIdx = $this->subject->canCreateItem();
$perfItem = DB::World()->selectRow('SELECT `perfectItemType` AS "itemId", `requiredSpecialization` AS "reqSpellId", `perfectCreateChance` AS "chance" FROM skill_perfect_item_template WHERE `spellId` = ?d', $this->typeId);
$scaling = $this->calculateEffectScaling();
// Iterate through all effects:
for ($i = 1; $i < 4; $i++)
@@ -1561,7 +1605,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
*/
$_nameEffect = $_nameAura = $_nameMV = $_nameMVB = $_markup = '';
$_icon = $_perfItem = $_footer = [];
$_icon = $_perfItem = $_footer = [];
$_footer['value'] = [0, 0];
$valueFmt = '%s';
@@ -1578,7 +1622,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
Type::ITEM,
$itemId,
$itemEntry ? $this->subject->relItems->getField('name', true) : Util::ucFirst(Lang::game('item')).' #'.$itemId,
($effBP + 1) . ($effDS > 1 ? '-' . ($effBP + $effDS) : ''),
$this->createNumRange($effBP, $effDS),
quality: $itemEntry ? $this->subject->relItems->getField('quality') : '',
link: !empty($itemEntry)
);
@@ -1591,7 +1635,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (!$cndSpell->error)
{
$_perfItem = array(
'spellId' => $perfItem['reqSpellId'],
'spellId' => $cndSpell->id,
'spellName' => $cndSpell->getField('name', true),
'icon' => $cndSpell->getField('iconString'),
'chance' => $perfItem['chance'],
@@ -1604,6 +1648,26 @@ class SpellBaseResponse extends TemplateResponse implements ICache
);
}
}
else if ($extraItem = DB::World()->selectRow('SELECT * FROM skill_extra_item_template WHERE `spellid` = ?d', $this->typeId))
{
$cndSpell = new SpellList(array(['id', $extraItem['requiredSpecialization']]));
if (!$cndSpell->error)
{
$_perfItem = array(
'spellId' => $cndSpell->id,
'spellName' => $cndSpell->getField('name', true),
'icon' => $cndSpell->getField('iconString'),
'chance' => $extraItem['additionalCreateChance'],
'item' => new IconElement(
Type::ITEM,
$this->subject->relItems->id,
$this->subject->relItems->getField('name', true),
num: '+'.$this->createNumRange($effBP, $effDS, $extraItem['additionalMaxNum']),
quality: $this->subject->relItems->getField('quality')
)
);
}
}
}
// .. from spell
else if (in_array($i, $spellIdx) || $effId == SPELL_EFFECT_UNLEARN_SPECIALIZATION)
@@ -1638,16 +1702,16 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$_footer['radius'] = Lang::spell('_radius').$this->subject->getField('effect'.$i.'RadiusMax').' '.Lang::spell('_distUnit');
if ($this->subject->getField('effect'.$i.'Periode') > 0)
$_footer['interval'] = Lang::spell('_interval').Util::formatTime($this->subject->getField('effect'.$i.'Periode'));
$_footer['interval'] = Lang::spell('_interval').DateTime::formatTimeElapsedFloat($this->subject->getField('effect'.$i.'Periode'));
if ($_ = $this->subject->getField('effect'.$i.'Mechanic'))
$_footer['mechanic'] = Lang::game('mechanic').Lang::main('colon').Lang::game('me', $_);
if (in_array($i, $this->subject->canTriggerSpell()) && $procData['chance'] && $procData['chance'] < 100)
{
$_footer['proc'] = $procData['chance'] < 0 ? Lang::spell('ppm', [Lang::nf(-$procData['chance'], 1)]) : Lang::spell('procChance') . $procData['chance'] . '%';
$_footer['proc'] = $procData['chance'] < 0 ? Lang::spell('ppm', [-$procData['chance']]) : Lang::spell('procChance', [$procData['chance']]);
if ($procData['cooldown'])
$_footer['procCD'] = Lang::game('cooldown', [Util::formatTime($procData['cooldown'], true)]);
$_footer['procCD'] = Lang::game('cooldown', [DateTime::formatTimeElapsed($procData['cooldown'] * 1000)]);
}
// Effect Name
@@ -1892,6 +1956,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV);
break;
case SPELL_AURA_MOD_LANGUAGE:
case SPELL_AURA_COMPREHEND_LANGUAGE:
if ($_ = Lang::game('languages', $effMV))
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.$effMV);
break;
@@ -1967,8 +2032,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($_ = Lang::getMagicSchools($effMV))
$_nameMV = $this->fmtStaffTip($_, 'MiscValue: '.Util::asHex($effMV));
break;
case SPELL_AURA_MOD_SKILL:
case SPELL_AURA_MOD_SKILL_TALENT:
case SPELL_AURA_MOD_SKILL: // temp
case SPELL_AURA_MOD_SKILL_TALENT: // perm
$valueFmt = '%+d';
if ($a = SkillList::makeLink($effMV))
$_nameMV = $a;
else
@@ -2137,6 +2203,21 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (isset($_footer['value'][2]))
$buffer .= $_footer['value'][2];
if (in_array($effId, SpellList::EFFECTS_SCALING_DAMAGE))
{
if ($scaling[2])
$buffer .= Lang::spell('apMod', [$scaling[2]]);
if ($scaling[0])
$buffer .= Lang::spell('spMod', [$scaling[0]]);
}
if (in_array($effAura, SpellList::AURAS_SCALING_DAMAGE))
{
if ($scaling[3])
$buffer .= Lang::spell('apMod', [$scaling[3]]);
if ($scaling[1])
$buffer .= Lang::spell('spMod', [$scaling[1]]);
}
$_footer['value'] = $buffer;
}
else
@@ -2197,10 +2278,16 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$this->attributes = $list;
}
private function createNumRange(int $bp, int $ds, int $mult = 1) : string
{
return Util::createNumRange($bp + 1, ($bp + $ds) * $mult, '-');
}
private function generatePath()
{
$cat = $this->subject->getField('typeCat');
$cf = $this->subject->getField('cuFlags');
$sl = $this->subject->getField('skillLines');
$this->breadcrumb[] = $cat;
@@ -2222,14 +2309,15 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if ($cat == -13)
$this->breadcrumb[] = ($cf & (SPELL_CU_GLYPH_MAJOR | SPELL_CU_GLYPH_MINOR)) >> 6;
else
$this->breadcrumb[] = $this->subject->getField('skillLines')[0];
else if ($sl)
$this->breadcrumb[] = $sl[0];
break;
case 9:
case -3:
case 11:
$this->breadcrumb[] = $this->subject->getField('skillLines')[0];
if ($sl)
$this->breadcrumb[] = $sl[0];
if ($cat == 11)
if ($_ = $this->subject->getField('reqSpellId'))
@@ -2238,7 +2326,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
break;
case -11:
foreach (SpellList::$skillLines as $line => $skills)
if (in_array($this->subject->getField('skillLines')[0], $skills))
if (in_array($sl[0] ?? [], $skills))
$this->breadcrumb[] = $line;
break;
case -7: // only spells unique in skillLineAbility will always point to the right skillLine :/
@@ -2263,8 +2351,9 @@ class SpellBaseResponse extends TemplateResponse implements ICache
private function createInfobox() : void
{
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
$typeCat = $this->subject->getField('typeCat');
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
$typeCat = $this->subject->getField('typeCat');
$hasCompletion = in_array($typeCat, [-5, -6]) && !($this->subject->getField('cuFlags') & CUSTOM_EXCLUDE_FOR_LISTVIEW);
// level
if (!in_array($typeCat, [-5, -6])) // not mount or vanity pet
@@ -2309,11 +2398,11 @@ class SpellBaseResponse extends TemplateResponse implements ICache
if (in_array($typeCat, [9, 11]))
{
// skill
if ($_ = $this->subject->getField('skillLines')[0])
if ($_ = $this->subject->getField('skillLines'))
{
$this->extendGlobalIds(Type::SKILL, $_);
$this->extendGlobalIds(Type::SKILL, $_[0]);
$bar = Lang::game('requires', ['&nbsp;[skill='.$_.']']);
$bar = Lang::game('requires', ['&nbsp;[skill='.$_[0].']']);
if ($_ = $this->subject->getField('learnedAt'))
$bar .= ' ('.$_.')';
@@ -2333,29 +2422,43 @@ class SpellBaseResponse extends TemplateResponse implements ICache
}
// accquisition.. 10: starter spell; 7: discovery
if ($this->subject->getSources($s))
{
if (in_array(SRC_STARTER, $s))
$infobox[] = Lang::spell('starter');
else if (in_array(SRC_DISCOVERY, $s))
$infobox[] = Lang::spell('discovered');
}
if ($this->subject->getRawSource(SRC_STARTER))
$infobox[] = Lang::spell('starter');
else if ($this->subject->getRawSource(SRC_DISCOVERY))
$infobox[] = Lang::spell('discovered');
// training cost
if ($cost = $this->subject->getField('trainingCost'))
$infobox[] = Lang::spell('trainingCost').'[money='.$cost.']';
// id
$infobox[] = Lang::spell('id') . $this->typeId;
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(Lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
}
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE') && $hasCompletion)
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_spells WHERE `spellId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
// used in mode
foreach ($this->difficulties as $n => $id)
if ($id == $this->typeId)
$infobox[] = Lang::game('mode').Lang::game('modes', $n);
$infobox[] = Lang::game('mode').Lang::game('modes', $this->mapType, $n);
// Creature Type from Aura: Shapeshift
foreach ($this->modelInfo as $mI)
@@ -2375,7 +2478,7 @@ class SpellBaseResponse extends TemplateResponse implements ICache
$infobox[] = 'Script'.Lang::main('colon').$_;
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', $hasCompletion);
// append glyph symbol if available
$glyphId = 0;

View File

@@ -26,7 +26,7 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
);
protected int $type = Type::SPELL;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'spells';
protected string $pageName = 'spells';
@@ -99,6 +99,12 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
$this->filter = new SpellListFilter($this->_get['filter'] ?? '', ['parentCats' => $this->category]);
if ($this->filter->shouldReload)
{
$_SESSION['error']['fi'] = $this->filter::class;
$get = $this->filter->buildGETParam();
$this->forward('?' . $this->pageName . $this->subCat . ($get ? '&filter=' . $get : ''));
}
$this->filterError = $this->filter->error;
}
@@ -110,13 +116,9 @@ class SpellsBaseResponse extends TemplateResponse implements ICache
if (!User::isInGroup(U_GROUP_EMPLOYEE))
$conditions[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0];
$this->filter->evalCriteria();
if ($_ = $this->filter->getConditions())
$conditions[] = $_;
$this->filterError = $this->filter->error; // maybe the evalX() caused something
/*************/
/* Menu Path */

View File

@@ -10,7 +10,7 @@ class TitleBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected array $breadcrumb = [0, 10];
@@ -84,8 +84,25 @@ class TitleBaseResponse extends TemplateResponse implements ICache
$infobox[] = Lang::game('eventShort', ['[event='.$eId.']']);
}
// id
$infobox[] = Lang::title('id') . $this->typeId;
// profiler relateed (note that this is part of the cache. I don't think this is important enough to calc for every view)
if (Cfg::get('PROFILER_ENABLE'))
{
$x = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_completion_titles WHERE `titleId` = ?d', $this->typeId);
$y = DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_profiler_profiles WHERE `custom` = 0 AND `stub` = 0');
$infobox[] = Lang::profiler('attainedBy', [round(($x ?: 0) * 100 / ($y ?: 1))]);
// completion row added by InfoboxMarkup
}
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0');
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0', 1);
/****************/

View File

@@ -11,7 +11,7 @@ class TitlesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::TITLE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'titles';

View File

@@ -42,7 +42,11 @@ class UserBaseResponse extends TemplateResponse
$this->user = $user;
else
$this->generateNotFound(Lang::user('notFound', [$pageParam]));
}
// do not display system account
if (!$this->user['id'])
$this->generateNotFound(Lang::user('notFound', [$pageParam]));
}
protected function generate() : void
{
@@ -70,14 +74,16 @@ class UserBaseResponse extends TemplateResponse
}
if ($this->user['joinDate'])
$infobox[] = Lang::user('joinDate') . '[tooltip name=joinDate]'. date('l, G:i:s', $this->user['joinDate']). '[/tooltip][span class=tip tooltip=joinDate]'. date(Lang::main('dateFmtShort'), $this->user['joinDate']). '[/span]';
$infobox[] = Lang::user('joinDate') . '[tooltip name=joinDate]'. date('l, G:i:s', $this->user['joinDate']). '[/tooltip][span class=tip tooltip=joinDate]'.(new DateTime())->formatDate($this->user['joinDate']). '[/span]';
if ($this->user['prevLogin'])
$infobox[] = Lang::user('lastLogin') . '[tooltip name=lastLogin]'.date('l, G:i:s', $this->user['prevLogin']).'[/tooltip][span class=tip tooltip=lastLogin]'.date(Lang::main('dateFmtShort'), $this->user['prevLogin']).'[/span]';
$infobox[] = Lang::user('lastLogin') . '[tooltip name=lastLogin]'.date('l, G:i:s', $this->user['prevLogin']).'[/tooltip][span class=tip tooltip=lastLogin]'.(new DateTime())->formatDate($this->user['prevLogin']).'[/span]';
if ($groups)
$infobox[] = Lang::user('userGroups') . implode(', ', $groups);
$infobox[] = Lang::user('consecVisits'). $this->user['consecutiveVisits'];
$infobox[] = Lang::main('siteRep') . Lang::nf($this->user['sumRep']);
if ($this->user['sumRep'])
$infobox[] = Lang::main('siteRep') . Lang::nf($this->user['sumRep']);
if ($infobox)
$this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF], 'infobox-contents0');
@@ -229,30 +235,44 @@ class UserBaseResponse extends TemplateResponse
if (Cfg::get('PROFILER_ENABLE'))
{
$conditions = array(
['OR', ['cuFlags', PROFILER_CU_PUBLISHED, '&'], ['ap.extraFlags', PROFILER_CU_PUBLISHED, '&']],
[['cuFlags', PROFILER_CU_DELETED, '&'], 0],
['OR', ['user', $this->user['id']], ['ap.accountId', $this->user['id']]]
);
if (User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions = array_slice($conditions, 2);
else if (User::$id == $this->user['id'])
array_shift($conditions);
$conditions = [['user', $this->user['id']]];
if (User::$id != $this->user['id'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['cuFlags', PROFILER_CU_PUBLISHED, '&'];
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['deleted', 0];
$profiles = new LocalProfileList($conditions);
if (!$profiles->error)
{
$this->addDataLoader('weight-presets');
// Characters
if ($chars = $profiles->getListviewData(PROFILEINFO_CHARACTER | PROFILEINFO_USER))
$this->charactersLvData = array_values($chars);
// Profiles
if ($prof = $profiles->getListviewData(PROFILEINFO_PROFILE | PROFILEINFO_USER))
$this->profilesLvData = array_values($prof);
}
$conditions = [['ap.accountId', $this->user['id']]];
if (User::$id != $this->user['id'] && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$conditions[] = ['ap.extraFlags', PROFILER_CU_PUBLISHED, '&'];
$characters = new LocalProfileList($conditions);
if (!$characters->error)
{
$this->addDataLoader('weight-presets');
if ($chars = $characters->getListviewData(PROFILEINFO_CHARACTER | PROFILEINFO_USER))
$this->charactersLvData = array_values($chars);
}
// signatures
/* $this->lvTabs->addListviewTab(new Listview(array(
* 'id' => 'signatures',
* 'name' => '$LANG.tab_signatures',
* 'hiddenCols' => ['name','faction','location','guild'],
* 'extraCols' => ['$Listview.extraCols.signature'],
* 'onBeforeCreate' => '$Listview.funcBox.beforeUserSignatures',
* 'data' => [ ProfileList->getListviewData() ] // no extra signature related data observed
* ), 'profile'));
*/
}
// My Guides
@@ -280,6 +300,9 @@ class UserBaseResponse extends TemplateResponse
[$sum, $nRatings] = $co;
if (!$sum)
return null;
return Lang::user('comments').$sum.($nRatings ? ' [small]([tooltip=tooltip_totalratings]'.$nRatings.'[/tooltip])[/small]' : '');
}
@@ -295,6 +318,9 @@ class UserBaseResponse extends TemplateResponse
[$sum, $nSticky, $nPending] = $ss;
if (!$sum)
return null;
$buff = [];
if ($nSticky || $nPending)
{
@@ -323,6 +349,9 @@ class UserBaseResponse extends TemplateResponse
[$sum, $nSticky, $nPending] = $vi;
if (!$sum)
return null;
$buff = [];
if ($nSticky || $nPending)
{
@@ -355,6 +384,9 @@ class UserBaseResponse extends TemplateResponse
if ($nReplies)
$buff[] = '[tooltip=replies]'.$nReplies.'[/tooltip]';
if (!$buff)
return null;
return Lang::user('posts').($nTopics + $nReplies).($buff ? ' [small]('.implode(' + ', $buff).')[/small]' : '');
}
}

View File

@@ -10,7 +10,7 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
{
use TrDetailPage, TrCache;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_DETAIL_PAGE;
protected string $template = 'detail-page-generic';
protected string $pageName = 'zone';
@@ -188,6 +188,13 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
}
}
// id
$infobox[] = Lang::zone('id') . $this->typeId;
// original name
if (Lang::getLocale() != Locale::EN)
$infobox[] = Util::ucFirst(Lang::lang(Locale::EN->value) . Lang::main('colon')) . '[copy button=false]'.$this->subject->getField('name_loc0').'[/copy][/li]';
if ($botRows = array_filter($quickFactsRows, fn($x) => $x > 0, ARRAY_FILTER_USE_KEY))
$infobox = array_merge($infobox, $botRows);
@@ -558,13 +565,55 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
$this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], 'tabsRelated', true);
// tab: Drops
if (in_array($this->subject->getField('category'), [MAP_TYPE_DUNGEON, MAP_TYPE_RAID]))
{
// Issue 1 - if the bosses drop items that are also sold by vendors moreZoneId will be 0 as vendor location and boss location are likely in conflict with each other
// Issue 2 - if the boss/chest isn't spawned the loot will not show up
$items = new ItemList(array(Cfg::get('SQL_LIMIT_NONE'), ['src.moreZoneId', $this->typeId], ['src.src2', 0, '>'], ['quality', ITEM_QUALITY_UNCOMMON, '>=']), ['calcTotal' => true]);
$data = $items->getListviewData();
$subTabs = false;
foreach ($items->iterate() as $id => $__)
{
$src = $items->getRawSource(SRC_DROP);
$map = ($items->getField('moreMask') ?: 0) & (SRC_FLAG_DUNGEON_DROP | SRC_FLAG_RAID_DROP);
if (!$src || !$map)
continue;
$subTabs = true;
if ($map & SRC_FLAG_RAID_DROP)
$mode = ($src[0] << 3);
else
$mode = ($src[0] & 0x1 ? 0x2 : 0) | ($src[0] & 0x2 ? 0x1 : 0);
$data[$id] += ['modes' => ['mode' => $mode]];
}
$tabData = array(
'data' => $data,
'id' => 'drops',
'name' => '$LANG.tab_drops',
'extraCols' => $subTabs ? ['$Listview.extraCols.mode'] : null,
'computeDataFunc' => '$Listview.funcBox.initLootTable',
'onAfterCreate' => $subTabs ? '$Listview.funcBox.addModeIndicator' : null
);
if (!is_null(ItemListFilter::getCriteriaIndex(16, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?items&filter=cr=16;crs='.$this->typeId.';crv=0');
$this->extendGlobalData($items->getJSGlobals(GLOBALINFO_SELF));
$this->lvTabs->addListviewTab(new Listview($tabData, ItemList::$brickFile));
}
// tab: NPCs
if ($cSpawns && !$creatureSpawns->error)
{
$tabData = array(
'data' => $creatureSpawns->getListviewData(),
'note' => sprintf(Util::$filterResultString, '?npcs&filter=cr=6;crs='.$this->typeId.';crv=0')
);
$tabData = ['data' => $creatureSpawns->getListviewData()];
if (!is_null(CreatureListFilter::getCriteriaIndex(6, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?npcs&filter=cr=6;crs='.$this->typeId.';crv=0');
if ($creatureSpawns->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['_truncated'] = 1;
@@ -577,10 +626,10 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
// tab: Objects
if ($oSpawns && !$objectSpawns->error)
{
$tabData = array(
'data' => $objectSpawns->getListviewData(),
'note' => sprintf(Util::$filterResultString, '?objects&filter=cr=1;crs='.$this->typeId.';crv=0')
);
$tabData = ['data' => $objectSpawns->getListviewData()];
if (!is_null(GameObjectListFilter::getCriteriaIndex(1, $this->typeId)))
$tabData['note'] = sprintf(Util::$filterResultString, '?objects&filter=cr=1;crs='.$this->typeId.';crv=0');
if ($objectSpawns->getMatches() > Cfg::get('SQL_LIMIT_DEFAULT'))
$tabData['_truncated'] = 1;
@@ -616,7 +665,10 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
if (!in_array($this->typeId, $children))
continue;
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_zonequests, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'", '.$this->typeId.')';
if (!is_null(ItemListFilter::getCriteriaIndex(126, $this->typeId)))
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_zonequests, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'", '.$this->typeId.')';
else
$tabData['note'] = '$$WH.sprintf(LANG.lvnote_questsind, '.$parent.', '.$this->typeId.',"'.$this->subject->getField('name', true).'")';
break;
}
@@ -655,11 +707,15 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
$rewards = new ItemList(array(['id', array_unique($rewardsLV)]));
if (!$rewards->error)
{
$note = null;
if (!is_null(ItemListFilter::getCriteriaIndex(126, $this->typeId)))
$note = sprintf(Util::$filterResultString, '?items&filter=cr=126;crs='.$this->typeId.';crv=0');
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $rewards->getListviewData(),
'name' => '$LANG.tab_questrewards',
'id' => 'quest-rewards',
'note' => sprintf(Util::$filterResultString, '?items&filter=cr=126;crs='.$this->typeId.';crv=0')
'note' => $note
), ItemList::$brickFile));
$this->extendGlobalData($rewards->getJSGlobals(GLOBALINFO_SELF));
@@ -669,8 +725,8 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
// tab: achievements
// tab: fished in zone
$fish = new Loot();
if ($fish->getByContainer(LOOT_FISHING, $this->typeId))
$fish = new LootByContainer();
if ($fish->getByContainer(Loot::FISHING, [$this->typeId]))
{
$this->extendGlobalData($fish->jsGlobals);
$xCols = array_merge(['$Listview.extraCols.percent'], $fish->extraCols);
@@ -682,12 +738,13 @@ class ZoneBaseResponse extends TemplateResponse implements ICache
$note = sprintf(Util::$lvTabNoteString, Lang::zone('fishingSkill'), Lang::formatSkillBreakpoints(Game::getBreakpointsForSkill(SKILL_FISHING, $skill), Lang::FMT_HTML));
$this->lvTabs->addListviewTab(new Listview(array(
'data' => $fish->getResult(),
'name' => '$LANG.tab_fishing',
'id' => 'fishing',
'extraCols' => array_unique($xCols),
'hiddenCols' => ['side'],
'note' => $note
'data' => $fish->getResult(),
'name' => '$LANG.tab_fishing',
'id' => 'fishing',
'extraCols' => array_unique($xCols),
'hiddenCols' => ['side'],
'note' => $note,
'computeDataFunc' => '$Listview.funcBox.initLootTable'
), ItemList::$brickFile));
}

View File

@@ -11,7 +11,7 @@ class ZonesBaseResponse extends TemplateResponse implements ICache
use TrListPage, TrCache;
protected int $type = Type::ZONE;
protected int $cacheType = CACHE_TYPE_PAGE;
protected int $cacheType = CACHE_TYPE_LIST_PAGE;
protected string $template = 'list-page-generic';
protected string $pageName = 'zones';

View File

@@ -221,29 +221,26 @@ class Conditions
/* IN */
/******/
public function getBySourceEntry(int $entry, int ...$srcType) : self
public function getBySource(int|array $type, int|array $group = 0, int|array $entry = 0, int|array $id = 0) : self
{
if ($group)
$group = is_int($group) ? [$group] : array_map('intVal', $group);
if ($entry)
$entry = is_int($entry) ? [$entry] : array_map('intVal', $entry);
if ($id)
$id = is_int($id) ? [$id] : array_map('intVal', $id);
if ($type)
$type = is_int($type) ? [$type] : array_map('intVal', $type);
else
return $this;
$this->rows = array_merge($this->rows, DB::World()->select(
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
FROM conditions
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceEntry` = ?d
WHERE `SourceTypeOrReferenceId` IN (?a){ AND `SourceGroup` IN (?a)}{ AND `SourceEntry` IN (?a)}{ AND `SourceId` IN (?a)}
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
$srcType, $entry
));
return $this;
}
public function getBySourceGroup(int $group, int ...$srcType) : self
{
$this->rows = array_merge($this->rows, DB::World()->select(
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
FROM conditions
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceGroup` = ?d
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
$srcType, $group
$type, $group ?: DBSIMPLE_SKIP, $entry ?: DBSIMPLE_SKIP, $id ?: DBSIMPLE_SKIP
));
return $this;
@@ -386,6 +383,14 @@ class Conditions
return $success;
}
public function toMarkupTag() : string
{
if (!$this->result)
return '';
return '[condition]' . json_encode($this->result, JSON_NUMERIC_CHECK) . '[/condition]';
}
public function getJsGlobals() : array
{
return $this->jsGlobals;
@@ -398,22 +403,22 @@ class Conditions
public static function lootTableToConditionSource(string $lootTable) : int
{
switch ($lootTable)
return match ($lootTable)
{
case LOOT_FISHING: return self::SRC_FISHING_LOOT_TEMPLATE;
case LOOT_CREATURE: return self::SRC_CREATURE_LOOT_TEMPLATE;
case LOOT_GAMEOBJECT: return self::SRC_GAMEOBJECT_LOOT_TEMPLATE;
case LOOT_ITEM: return self::SRC_ITEM_LOOT_TEMPLATE;
case LOOT_DISENCHANT: return self::SRC_DISENCHANT_LOOT_TEMPLATE;
case LOOT_PROSPECTING: return self::SRC_PROSPECTING_LOOT_TEMPLATE;
case LOOT_MILLING: return self::SRC_MILLING_LOOT_TEMPLATE;
case LOOT_PICKPOCKET: return self::SRC_PICKPOCKETING_LOOT_TEMPLATE;
case LOOT_SKINNING: return self::SRC_SKINNING_LOOT_TEMPLATE;
case LOOT_MAIL: return self::SRC_MAIL_LOOT_TEMPLATE;
case LOOT_SPELL: return self::SRC_SPELL_LOOT_TEMPLATE;
case LOOT_REFERENCE: return self::SRC_REFERENCE_LOOT_TEMPLATE;
default: return self::SRC_NONE;
}
Loot::FISHING => self::SRC_FISHING_LOOT_TEMPLATE,
Loot::CREATURE => self::SRC_CREATURE_LOOT_TEMPLATE,
Loot::GAMEOBJECT => self::SRC_GAMEOBJECT_LOOT_TEMPLATE,
Loot::ITEM => self::SRC_ITEM_LOOT_TEMPLATE,
Loot::DISENCHANT => self::SRC_DISENCHANT_LOOT_TEMPLATE,
Loot::PROSPECTING => self::SRC_PROSPECTING_LOOT_TEMPLATE,
Loot::MILLING => self::SRC_MILLING_LOOT_TEMPLATE,
Loot::PICKPOCKET => self::SRC_PICKPOCKETING_LOOT_TEMPLATE,
Loot::SKINNING => self::SRC_SKINNING_LOOT_TEMPLATE,
Loot::MAIL => self::SRC_MAIL_LOOT_TEMPLATE,
Loot::SPELL => self::SRC_SPELL_LOOT_TEMPLATE,
Loot::REFERENCE => self::SRC_REFERENCE_LOOT_TEMPLATE,
default => self::SRC_NONE
};
}
public static function extendListviewRow(array &$lvRow, int $srcType, int $groupKey, array $condition) : bool
@@ -486,9 +491,9 @@ class Conditions
$this->jsGlobals[$grp][$sGroup] = $sGroup;
if (is_int($entry))
$this->jsGlobals[$entry][$sEntry] = $sEntry;
// Note: sourceId currently has no typed content
// if (is_int($id))
// $this->jsGlobals[$id][$sId] = $sId;
// Note: sourceId currently has no typed content
// if (is_int($id))
// $this->jsGlobals[$id][$sId] = $sId;
// more checks? not all sources can retarget
$cTarget = min(1, max(0, $cTarget));

View File

@@ -21,14 +21,10 @@ trait SmartHelper
private function numRange(int $min, int $max, bool $isTime) : string
{
if (!$min && !$max)
return '';
if ($isTime)
return Util::createNumRange($min, $max, ' &ndash; ', fn($x) => DateTime::formatTimeElapsed($x));
$str = $isTime ? Util::formatTime($min, true) : $min;
if ($max > $min)
$str .= ' &ndash; '.($isTime ? Util::formatTime($max, true) : $max);
return $str;
return Util::createNumRange($min, $max, ' &ndash; ');
}
private function formatTime(int $time, int $_, bool $isMilliSec) : string
@@ -36,11 +32,17 @@ trait SmartHelper
if (!$time)
return '';
return Util::formatTime($time * ($isMilliSec ? 1 : 1000), false);
return DateTime::formatTimeElapsedFloat($time * ($isMilliSec ? 1 : 1000));
}
private function castFlags(int $flags) : string
{
if ($x = ($flags & ~SmartAI::CAST_FLAG_VALIDATE))
{
trigger_error('SmartAI::castFlags - unknown SmartCastFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= SmartAI::CAST_FLAG_VALIDATE;
}
$cf = [];
for ($i = 1; $i <= SmartAI::CAST_FLAG_COMBAT_MOVE; $i <<= 1)
if (($flags & $i) && ($x = Lang::smartAI('castFlags', $i)))
@@ -51,6 +53,12 @@ trait SmartHelper
private function npcFlags(int $flags) : string
{
if ($x = ($flags & ~NPC_FLAG_VALIDATE))
{
trigger_error('SmartAI::npcFlags - unknown NpcFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= NPC_FLAG_VALIDATE;
}
$nf = [];
for ($i = 1; $i <= NPC_FLAG_MAILBOX; $i <<= 1)
if (($flags & $i) && ($x = Lang::npc('npcFlags', $i)))
@@ -61,6 +69,12 @@ trait SmartHelper
private function dynFlags(int $flags) : string
{
if ($x = ($flags & ~UNIT_DYNFLAG_VALIDATE))
{
trigger_error('SmartAI::dynFlags - unknown unit dynFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= UNIT_DYNFLAG_VALIDATE;
}
$df = [];
for ($i = 1; $i <= UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST; $i <<= 1)
if (($flags & $i) && ($x = Lang::unit('dynFlags', $i)))
@@ -71,6 +85,12 @@ trait SmartHelper
private function goFlags(int $flags) : string
{
if ($x = ($flags & ~GO_FLAG_VALIDATE))
{
trigger_error('SmartAI::goFlags - unknown GameobjectFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= GO_FLAG_VALIDATE;
}
$gf = [];
for ($i = 1; $i <= GO_FLAG_DESTROYED; $i <<= 1)
if (($flags & $i) && ($x = Lang::gameObject('goFlags', $i)))
@@ -81,6 +101,12 @@ trait SmartHelper
private function spawnFlags(int $flags) : string
{
if ($x = ($flags & ~SmartAI::SPAWN_FLAG_VALIDATE))
{
trigger_error('SmartAI::spawnFlags - unknown SmartSpawnFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= SmartAI::SPAWN_FLAG_VALIDATE;
}
$sf = [];
for ($i = 1; $i <= SmartAI::SPAWN_FLAG_NOSAVE_RESPAWN; $i <<= 1)
if (($flags & $i) && ($x = Lang::smartAI('spawnFlags', $i)))
@@ -91,6 +117,18 @@ trait SmartHelper
private function unitFlags(int $flags, int $flags2) : string
{
if ($x = ($flags & ~UNIT_FLAG_VALIDATE))
{
trigger_error('SmartAI::unitFlags - unknown UnitFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= UNIT_FLAG_VALIDATE;
}
if ($x = ($flags2 & ~UNIT_FLAG2_VALIDATE))
{
trigger_error('SmartAI::unitFlags - unknown UnitFlags2 '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags2 &= UNIT_FLAG2_VALIDATE;
}
$field = $flags2 ? 'flags2' : 'flags';
$max = $flags2 ? UNIT_FLAG2_ALLOW_CHEAT_SPELLS : UNIT_FLAG_UNK_31;
$uf = [];
@@ -185,6 +223,7 @@ class SmartAI
// public const CAST_FORCE_TARGET_SELF = 0x10; // the target to cast this spell on itself
public const CAST_FLAG_AURA_MISSING = 0x20; // Only casts the spell if the target does not have an aura from the spell
public const CAST_FLAG_COMBAT_MOVE = 0x40; // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
public const CAST_FLAG_VALIDATE = self::CAST_FLAG_INTERRUPT_PREV | self::CAST_FLAG_TRIGGERED | self::CAST_FLAG_AURA_MISSING | self::CAST_FLAG_COMBAT_MOVE;
public const REACT_PASSIVE = 0;
public const REACT_DEFENSIVE = 1;
@@ -211,14 +250,23 @@ class SmartAI
public const SPAWN_FLAG_IGNORE_RESPAWN = 0x01; // onSpawnIn - ignore & reset respawn timer
public const SPAWN_FLAG_FORCE_SPAWN = 0x02; // onSpawnIn - force additional spawn if already in world
public const SPAWN_FLAG_NOSAVE_RESPAWN = 0x04; // onDespawn - remove respawn time
public const SPAWN_FLAG_VALIDATE = self::SPAWN_FLAG_IGNORE_RESPAWN | self::SPAWN_FLAG_FORCE_SPAWN | self::SPAWN_FLAG_NOSAVE_RESPAWN;
private array $jsGlobals = [];
private array $rawData = [];
private array $result = [];
private array $tabs = [];
private array $itr = [];
private array $quotes = [];
private array $quotes = [];
public string $css = <<<CSS
#smartai-generic .grid { clear:left; display: grid; }
#smartai-generic .tabbed-contents { padding:0px; clear:left; }
#smartai-generic .grid thead,
#smartai-generic .grid tbody,
#smartai-generic .grid tr { display: contents; }
#sai { display: grid; }
CSS;
// misc data
public readonly int $baseEntry; // I'm a timed action list belonging to this entry
@@ -231,24 +279,28 @@ class SmartAI
$this->title = $miscData['title'] ?? '';
$this->teleportTargetArea = $miscData['teleportTargetArea'] ?? 0;
if ($this->baseEntry) // my parent handles base css
$this->css = '';
$raw = DB::World()->select(
'SELECT `id`, `link`,
`event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`,
`action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`,
`target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`
FROM smart_scripts
WHERE `entryorguid` = ?d AND `source_type` = ?d
ORDER BY `id` ASC',
'SELECT `id`, `link`,
`event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`,
`action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`,
`target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`
FROM smart_scripts
WHERE `entryorguid` = ?d AND `source_type` = ?d
ORDER BY `id` ASC',
$this->entry, $this->srcType);
foreach ($raw as $r)
{
$this->rawData[$r['id']] = array(
'id' => $r['id'],
'link' => $r['link'],
'event' => new SmartEvent($r['id'], $r['event_type'], $r['event_phase_mask'], $r['event_chance'], $r['event_flags'], [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], $r['event_param5']], $this),
'action' => new SmartAction($r['id'], $r['action_type'], [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']], $this),
'target' => new SmartTarget($r['id'], $r['target_type'], [$r['target_param1'], $r['target_param2'], $r['target_param3'], $r['target_param4']], [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']], $this)
'id' => $r['id'],
'link' => $r['link'],
'event' => new SmartEvent($r['id'], $r['event_type'], $r['event_phase_mask'], $r['event_chance'], $r['event_flags'], [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], $r['event_param5']], $this),
'action' => new SmartAction($r['id'], $r['action_type'], [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']], $this),
'target' => new SmartTarget($r['id'], $r['target_type'], [$r['target_param1'], $r['target_param2'], $r['target_param3'], $r['target_param4']], [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']], $this),
'condition' => (new Conditions())->getBySource(Conditions::SRC_SMART_EVENT, $r['id'] + 1, $entry, $srcType)
);
}
}
@@ -579,10 +631,9 @@ class SmartAI
if ($this->result)
return true;
$hidePhase =
$hideChance = true;
$visibleCols = (1 << 0) | (1 << 2) | (1 << 4);
foreach ($this->iterate() as $id => $__)
foreach ($this->iterate() as $__)
{
$rowIdx = Util::createHash(8);
@@ -596,53 +647,60 @@ class SmartAI
$evtBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $evtBody);
$actBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $actBody);
if (!$this->itr['event']->hasPhases())
$hidePhase = false;
if ($this->itr['event']->hasPhases())
$visibleCols |= (1 << 1);
if ($this->itr['event']->chance != 100)
$hideChance = false;
$visibleCols |= (1 << 3);
if ($this->itr['condition']->prepare())
{
$visibleCols |= (1 << 5);
Util::mergeJsGlobals($this->jsGlobals, $this->itr['condition']->getJsGlobals());
}
$this->result[] = array(
$this->itr['id'],
implode(', ', Util::mask2bits($this->itr['event']->phaseMask, 1)),
$evtBody.($evtFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : null),
$evtBody.($evtFooter ? '[div float=right margin=0px clear=both width=100% align=right][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : ''),
$this->itr['event']->chance.'%',
$actBody.($actFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : null)
$actBody.($actFooter ? '[div float=right margin=0px clear=both width=100% align=right][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : ''),
$this->itr['condition']->toMarkupTag()
);
}
$th = array(
'#' => 16,
'Phase' => 32,
'Event' => 350,
'Chance' => 24,
'Action' => 0
['#' , '24px'],
['Phase', '48px'],
['Event', '30%%'],
['Chance', '60px'],
['Action', 'auto'],
['Condition', 'auto']
);
if ($hidePhase)
for ($i = 0, $j = count($th); $i < $j; $i++)
{
unset($th['Phase']);
foreach ($this->result as &$r)
unset($r[1]);
}
unset($r);
if ($visibleCols & (1 << $i))
continue;
if ($hideChance)
{
unset($th['Chance']);
unset($th[$i]);
foreach ($this->result as &$r)
unset($r[3]);
}
unset($r);
unset($r[$i]);
$tbl = '[tr]';
foreach ($th as $n => $w)
$tbl .= '[td header '.($w ? 'width='.$w.'px' : null).']'.$n.'[/td]';
$tbl .= '[/tr]';
unset($r);
}
$tblId = Util::createHash(12);
$this->css .= "\n#tbl-".$tblId." { grid-template-columns: ".implode(' ', array_column($th, 1))."; }";
$tbl = '[tr]' . array_reduce(array_column($th, 0), fn($out, $n) => $out .= '[td header]'.$n.'[/td]', '') . '[/tr]';
foreach ($this->result as $r)
$tbl .= '[tr][td]'.implode('[/td][td]', $r).'[/td][/tr]';
$tbl = '[table id=tbl-'.$tblId.' class=grid]'.$tbl.'[/table]';
if ($this->srcType == self::SRC_TYPE_ACTIONLIST)
$this->tabs[$this->entry] = $tbl;
else
@@ -658,16 +716,16 @@ class SmartAI
if (!$this->rawData)
return null;
$wrapper = '[table class=grid width=940px]%s[/table]';
$return = '[style]#smartai-generic .grid { clear:left; } #smartai-generic .tabbed-contents { padding:0px; clear:left; }[/style][pad][h3][toggler id=sai]SmartAI'.$this->title.'[/toggler][/h3][div id=sai clear=left]%s[/div]';
$wrapper = '%s';
$return = '[style]'.strtr($this->css, "\n", ' ').'[/style][pad][h3][toggler id=sai]SmartAI'.$this->title.'[/toggler][/h3][div id=sai clear=left]%s[/div]';
$tabs = '';
if (count($this->tabs) > 1)
{
$wrapper = '[tabs name=sai width=942px]%s[/tabs]';
$wrapper = '[tabs name=sai]%s[/tabs]';
$return = "[script]function TalTabClick(id) { $('#dsf67g4d-sai').find('[href=\'#sai-actionlist-' + id + '\']').click(); }[/script]" . $return;
foreach ($this->tabs as $guid => $data)
{
$buff = '[tab name="'.($guid ? 'ActionList #'.$guid : 'Main').'"][table class=grid width=940px]'.$data.'[/table][/tab]';
$buff = '[tab name="'.($guid ? 'ActionList #'.$guid : 'Main').'"]'.$data.'[/tab]';
if ($guid)
$tabs .= $buff;
else

View File

@@ -467,8 +467,8 @@ class SmartAction
WHERE tp.`id` = ?d',
Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, $this->param[0]
);
$this->param[10] = Util::jsEscape(Util::localizedString($nodes, 'start'));
$this->param[11] = Util::jsEscape(Util::localizedString($nodes, 'end'));
$this->param[10] = Util::localizedString($nodes, 'start');
$this->param[11] = Util::localizedString($nodes, 'end');
break;
case self::ACTION_SET_INGAME_PHASE_MASK: // 44 -> any target
if ($this->param[0])
@@ -562,6 +562,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[0], ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt)
@@ -587,6 +589,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[$i], ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt)
@@ -603,6 +607,8 @@ class SmartAction
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $i, ['baseEntry' => $this->smartAI->getEntry()]);
$tal->prepare();
$this->smartAI->css .= $tal->css;
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
foreach ($tal->getTabs() as $guid => $tt)

View File

@@ -98,6 +98,10 @@ class SmartEvent
public const EVENT_ON_SPELL_FAILED = 84; // On Unit::InterruptSpell
public const EVENT_ON_SPELL_START = 85; // On Spell::prapare
public const EVENT_ON_DESPAWN = 86; // On before creature removed
public const EVENT_SEND_EVENT_TRIGGER = 87; // [RESERVED] UNUSED NEEDS CHERRYPICK
public const EVENT_AREATRIGGER_EXIT = 88; // [RESERVED] don't use on 3.3.5a
public const EVENT_ON_AURA_APPLIED = 89; //
public const EVENT_ON_AURA_REMOVED = 90; //
public const FLAG_NO_REPEAT = 0x0001;
public const FLAG_DIFFICULTY_0 = 0x0002;
@@ -108,6 +112,7 @@ class SmartEvent
public const FLAG_NO_RESET = 0x0100;
public const FLAG_WHILE_CHARMED = 0x0200;
public const FLAG_ALL_DIFFICULTIES = self::FLAG_DIFFICULTY_0 | self::FLAG_DIFFICULTY_1 | self::FLAG_DIFFICULTY_2 | self::FLAG_DIFFICULTY_3;
public const FLAG_VALIDATE = self::FLAG_NO_REPEAT | self::FLAG_DEBUG_ONLY | self::FLAG_NO_RESET | self::FLAG_WHILE_CHARMED | self::FLAG_ALL_DIFFICULTIES;
private const EVENT_CELL_TPL = '[tooltip name=e-#rowIdx#]%1$s[/tooltip][span tooltip=e-#rowIdx#]%2$s[/span]';
@@ -198,7 +203,11 @@ class SmartEvent
self::EVENT_ON_SPELL_CAST => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_SPELL_FAILED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_SPELL_START => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0] // NONE
self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0], // NONE
self::EVENT_SEND_EVENT_TRIGGER => [null, null, null, null, null, 2], // UNUSED NEEDS CHERRYPICK
self::EVENT_AREATRIGGER_EXIT => [null, null, null, null, null, 2], // don't use on 3.3.5a
self::EVENT_ON_AURA_APPLIED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
self::EVENT_ON_AURA_REMOVED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0] // SpellID, CooldownMin, CooldownMax
);
private array $jsGlobals = [];
@@ -296,7 +305,7 @@ class SmartEvent
);
if ($gmo)
$this->param[10] = Util::jsEscape(Util::localizedString($gmo, 'text'));
$this->param[10] = Util::localizedString($gmo, 'text');
else
trigger_error('SmartAI::event - could not find gossip menu option for event #'.$this->type);
break;
@@ -359,13 +368,19 @@ class SmartEvent
public function hasPhases() : bool
{
return $this->phaseMask == 0;
return $this->phaseMask && ($this->phaseMask & 0xFFF) != 0xFFF;
}
private function formatFlags() : string
{
$flags = $this->flags;
if ($x = ($flags & ~self::FLAG_VALIDATE))
{
trigger_error('SmartEvent::formatFlags - unused SmartEventFlags '.Util::asBin($x).' set on id #'.$this->id, E_USER_NOTICE);
$flags &= self::FLAG_VALIDATE;
}
if (($flags & self::FLAG_ALL_DIFFICULTIES) == self::FLAG_ALL_DIFFICULTIES)
$flags &= ~self::FLAG_ALL_DIFFICULTIES;

View File

@@ -306,6 +306,11 @@ abstract class DBTypeList
$this->error = false;
}
/**
* iterate over fetched templates
*
* @return array the current template
*/
public function &iterate() : \Generator
{
if (!$this->templates)
@@ -483,7 +488,7 @@ abstract class DBTypeList
't': type [always set]
'ti': typeId [always set]
'bd': BossDrop [0; 1] [Creature / GO]
'dd': DungeonDifficulty [-2: DungeonHC; -1: DungeonNM; 1: Raid10NM; 2:Raid25NM; 3:Raid10HM; 4: Raid25HM] [Creature / GO]
'dd': DungeonDifficulty [-2: DungeonHC; -1: DungeonNM; 1: Raid10NM; 2:Raid25NM; 3:Raid10HM; 4: Raid25HM; 99: filler trash] [Creature / GO]
'q': cssQuality [Items]
'z': zone [set when all happens in here]
'p': PvP [pvpSourceId]
@@ -632,7 +637,18 @@ trait spawnHelper
$wpSum = [];
$wpIdx = 0;
$worldPos = [];
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) AND `posX` > 0 AND `posY` > 0", self::$type, $this->getFoundIDs()) ?: [];
$spawns = DB::Aowow()->select(
'SELECT CASE WHEN z.`type` = ?d THEN 1
WHEN z.`type` = ?d THEN 2
WHEN z.`type` = ?d THEN 2
ELSE 0
END AS "mapType", s.*
FROM ?_spawns s
JOIN ?_zones z ON s.areaId = z.id
WHERE s.`type` = ?d AND s.`typeId` IN (?a) AND s.`posX` > 0 AND s.`posY` > 0',
MAP_TYPE_DUNGEON_HC, MAP_TYPE_MMODE_RAID, MAP_TYPE_MMODE_RAID_HC,
self::$type, $this->getFoundIDs()
) ?: [];
if (!$skipAdmin && User::isInGroup(U_GROUP_MODERATOR))
if ($guids = array_column(array_filter($spawns, fn($x) => $x['guid'] > 0 || $x['type'] != Type::NPC), 'guid'))
@@ -653,7 +669,7 @@ trait spawnHelper
$label = [Lang::npc('waypoint').Lang::main('colon').$p['point']];
if ($p['wait'])
$label[] = Lang::npc('wait').Lang::main('colon').Util::formatTime($p['wait'], false);
$label[] = Lang::npc('wait').Lang::main('colon').DateTime::formatTimeElapsedFloat($p['wait']);
$opts = array( // \0 doesn't get printed and tricks Util::toJSON() into handling this as a string .. i feel slightly dirty now
'label' => "\0$<br /><span class=\"q0\">".implode('<br />', $label).'</span>',
@@ -696,13 +712,13 @@ trait spawnHelper
$info[2] = Lang::game('phases').Lang::main('colon').Util::asHex($s['phaseMask']);
if ($s['spawnMask'] == 15)
$info[3] = Lang::game('mode').Lang::game('modes', -1);
$info[3] = Lang::game('mode').Lang::game('modes', 0, -1);
else if ($s['spawnMask'])
{
$_ = [];
for ($i = 0; $i < 4; $i++)
if ($s['spawnMask'] & 1 << $i)
$_[] = Lang::game('modes', $i);
$_[] = Lang::game('modes', $s['mapType'], $i);
$info[4] = Lang::game('mode').implode(', ', $_);
}
@@ -880,8 +896,13 @@ trait profilerHelper
trait sourceHelper
{
protected $sources = [];
protected $sourceMore = null;
protected array $sources = [];
protected ?array $sourceMore = null;
public function getRawSource(int $src) : array
{
return $this->sources[$this->id][$src] ?? [];
}
public function getSources(?array &$s = [], ?array &$sm = []) : bool
{
@@ -923,7 +944,9 @@ trait sourceHelper
10H 0b0100 2 0b011
25H 0b1000 3 0b100
*/
if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP)
if ($this->curTpl['moreMask'] & SRC_FLAG_COMMON)
$sm['dd'] = 99;
else if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP)
$sm['dd'] = $this->sources[$this->id][SRC_DROP][0] * -1;
else if ($this->curTpl['moreMask'] & SRC_FLAG_RAID_DROP)
{

View File

@@ -63,7 +63,7 @@ abstract class Filter
protected const PATTERN_NAME = '/[\p{C};%\\\\]/ui';
protected const PATTERN_CRV = '/[\p{C};:%\\\\]/ui';
protected const PATTERN_INT = '/\D/';
public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/i';
public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/ui';
protected const ENUM_FACTION = array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92,
54, 946, 67, 1052, 749, 47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809,
@@ -82,7 +82,7 @@ abstract class Filter
3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848,
17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714,
3717, 3715, 717, 67, 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603,
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977);
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977, 4987);
protected const ENUM_HEROICDUNGEON = array( 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100,
4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196);
protected const ENUM_MULTIMODERAID = array( 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987);
@@ -91,12 +91,13 @@ abstract class Filter
protected const ENUM_RACE = array( null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false);
protected const ENUM_PROFESSION = array( null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773);
public bool $error = false; // erroneous search fields
public bool $error = false;
public bool $shouldReload = false; // erroneous params have been corrected. Build GET string and reload
// item related
public array $upgrades = []; // [itemId => slotId]
public array $extraOpts = []; // score for statWeights
public array $wtCnd = []; // DBType condition for statWeights
public array $upgrades = []; // [itemId => slotId]
public array $extraOpts = []; // score for statWeights
public array $wtCnd = []; // DBType condition for statWeights
private array $cndSet = []; // db type query storage
private array $rawData = [];
@@ -106,7 +107,7 @@ abstract class Filter
[self::CR_FLAG, <string:colName>, <int:testBit>, <bool:matchAny>] # default param2: matchExact
[self::CR_NUMERIC, <string:colName>, <int:NUM_FLAGS>, <bool:addExtraCol>]
[self::CR_STRING, <string:colName>, <int:STR_FLAGS>, null]
[self::CR_ENUM, <string:colName>, <bool:ANYNONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
[self::CR_ENUM, <string:colName>, <bool:ANY_NONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
[self::CR_STAFFFLAG, <string:colName>, null, null]
[self::CR_CALLBACK, <string:fnName>, <mixed:param1>, <mixed:param2>]
[self::CR_NYI_PH, null, <int:returnVal>, param2] # mostly 1: to ignore this criterium; 0: to fail the whole query
@@ -126,8 +127,7 @@ abstract class Filter
public array $fiReputationCols = []; // fn params ([[factionId, factionName], ...])
public array $fiExtraCols = []; //
public string $query = ''; // as in url query params
public array $values = []; // old fiData['v']
public array $criteria = []; // old fiData['c']
public array $values = []; // prefiltered rawData
// parse the provided request into a usable format
public function __construct(string|array $data, array $opts = [])
@@ -157,6 +157,8 @@ abstract class Filter
}
$this->initFields();
$this->evalCriteria();
$this->evalWeights();
}
public function mergeCat(array &$cats) : void
@@ -167,13 +169,13 @@ abstract class Filter
private function &criteriaIterator() : \Generator
{
if (!$this->criteria)
if (empty($this->values['cr']))
return;
for ($i = 0; $i < count($this->criteria['cr']); $i++)
for ($i = 0; $i < count($this->values['cr']); $i++)
{
// throws a notice if yielded directly "Only variable references should be yielded by reference"
$v = [&$this->criteria['cr'][$i], &$this->criteria['crs'][$i], &$this->criteria['crv'][$i]];
$v = [&$this->values['cr'][$i], &$this->values['crs'][$i], &$this->values['crv'][$i]];
yield $i => $v;
}
}
@@ -195,7 +197,7 @@ abstract class Filter
public function buildGETParam(array $override = [], array $addCr = []) : string
{
$get = [];
foreach (array_merge($this->criteria, $this->values, $override) as $k => $v)
foreach (array_merge($this->values, $override) as $k => $v)
{
if (isset($addCr[$k]))
{
@@ -228,7 +230,7 @@ abstract class Filter
$this->cndSet = $this->createSQLForValues();
// criteria
foreach ($this->criteriaIterator() as &$_cr)
foreach ($this->criteriaIterator() as $_cr)
if ($cnd = $this->createSQLForCriterium(...$_cr))
$this->cndSet[] = $cnd;
@@ -241,10 +243,10 @@ abstract class Filter
public function getSetCriteria(int ...$cr) : array
{
if (!$cr || !$this->fiSetCriteria)
return $this->fiSetCriteria;
if (!$cr || empty($this->values['cr']))
return [];
return array_values(array_intersect($this->fiSetCriteria['cr'], $cr));
return array_values(array_intersect($this->values['cr'], $cr));
}
@@ -258,12 +260,17 @@ abstract class Filter
return [];
$data = [];
// someone copy/pasted a WH filter
$get = preg_replace('/^(\d+(:\d+)*);(\d+(:\d+)*);(\P{C}+(:\P{C}+)*)$/', 'cr=$1;crs=$3;crv=$5', $get);
foreach (explode(';', $get) as $field)
{
if (!strstr($field, '='))
{
trigger_error('Filter::transformGET - malformed GET string', E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
continue;
}
@@ -272,7 +279,8 @@ abstract class Filter
if (!isset(static::$inputFields[$k]))
{
trigger_error('Filter::transformGET - GET param not in filter: '.$k, E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
continue;
}
@@ -286,13 +294,24 @@ abstract class Filter
private function initFields() : void
{
/* quirks:
* - in the POST step there may be excess criteria selectors with a value of '', as unselecting a criteria that is not the last will not remove the row from the UI
* - if there are no criteria selected, the placeholder selection will always be sent as ['', null, null], similar to the previous quirk
*
* same for stat weights on ItemListFilter
*/
if (!empty($this->rawData['cr']))
$this->rawData['cr'] = array_filter($this->rawData['cr'], fn($x) => $x !== '') ?: null;
if (!empty($this->rawData['wt']))
$this->rawData['wt'] = array_filter($this->rawData['wt'], fn($x) => $x !== '') ?: null;
$cleanupCr = [];
foreach (static::$inputFields as $inp => [$type, $valid, $asArray])
{
$var = in_array($inp, ['cr', 'crs', 'crv']) ? 'criteria' : 'values';
if (!isset($this->rawData[$inp]) || $this->rawData[$inp] === '')
{
$this->$var[$inp] = $asArray ? [] : null;
$this->values[$inp] = $asArray ? [] : null;
continue;
}
@@ -300,42 +319,62 @@ abstract class Filter
if ($asArray)
{
// quirk: in the POST step criteria can be [[''], null, null] if not selected.
$buff = [];
foreach ((array)$val as $v) // can be string|int in POST step if only one value present
if ($v !== '' && $this->checkInput($type, $valid, $v))
foreach ((array)$val as $i => $v) // can be string|int in POST step if only one value present
{
if (in_array($inp, ['cr', 'crs', 'crv']))
{
if (!$this->checkInput($type, $valid, $v))
$cleanupCr[] = $i;
$buff[] = $v; // always assign, gets removed later as tuple
}
else if ($this->checkInput($type, $valid, $v))
$buff[] = $v;
}
$this->$var[$inp] = $buff;
$this->values[$inp] = $buff;
}
else
$this->$var[$inp] = $this->checkInput($type, $valid, $val) ? $val : null;
$this->values[$inp] = $this->checkInput($type, $valid, $val) ? $val : null;
}
if ($cleanupCr)
{
$this->error =
$this->shouldReload = true;
foreach (array_unique($cleanupCr) as $i)
unset($this->values['cr'][$i], $this->values['crs'][$i], $this->values['crv'][$i]);
$this->values['cr'] = array_values($this->values['cr']);
$this->values['crs'] = array_values($this->values['crs']);
$this->values['crv'] = array_values($this->values['crv']);
}
}
public function evalCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
private function evalCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
{
if (empty($this->criteria['cr']) && empty($this->criteria['crs']) && empty($this->criteria['crv']))
if (empty($this->values['cr']) && empty($this->values['crs']) && empty($this->values['crv']))
return;
else if (empty($this->criteria['cr']) || empty($this->criteria['crs']) || empty($this->criteria['crv']))
{
unset($this->criteria['cr']);
unset($this->criteria['crs']);
unset($this->criteria['crv']);
trigger_error('Filter::setCriteria - one of cr, crs, crv is missing', E_USER_NOTICE);
$this->error = true;
if (empty($this->values['cr']) || empty($this->values['crs']) || empty($this->values['crv']))
{
trigger_error('Filter::evalCriteria - one of cr, crs, crv is missing', E_USER_NOTICE);
unset($this->values['cr'], $this->values['crs'], $this->values['crv']);
$this->error =
$this->shouldReload = true;
return;
}
$_cr = &$this->criteria['cr'];
$_crs = &$this->criteria['crs'];
$_crv = &$this->criteria['crv'];
$_cr = &$this->values['cr'];
$_crs = &$this->values['crs'];
$_crv = &$this->values['crv'];
if (count($_cr) != count($_crv) || count($_cr) != count($_crs) || count($_cr) > 5 || count($_crs) > 5 /*|| count($_crv) > 5*/)
{
// use min provided criterion as basis; 5 criteria at most
$min = max(5, min(count($_cr), count($_crv), count($_crs)));
$min = min(5, count($_cr), count($_crv), count($_crs));
if (count($_cr) > $min)
array_splice($_cr, $min);
@@ -345,70 +384,88 @@ abstract class Filter
if (count($_crs) > $min)
array_splice($_crs, $min);
trigger_error('Filter::setCriteria - cr, crs, crv are imbalanced', E_USER_NOTICE);
$this->error = true;
trigger_error('Filter::evalCriteria - cr, crs, crv are imbalanced', E_USER_NOTICE);
$this->error =
$this->shouldReload = true;
}
for ($i = 0; $i < count($_cr); $i++)
{
// conduct filter specific checks & casts here
$unsetme = false;
if (isset(static::$genericFilter[$_cr[$i]]))
if (!isset(static::$genericFilter[$_cr[$i]]) || $_crs[$i] === '' || $_crv[$i] === '')
{
$gf = static::$genericFilter[$_cr[$i]];
switch ($gf[0])
{
case self::CR_NUMERIC:
$_ = $_crs[$i];
if (!Util::checkNumeric($_crv[$i], $gf[2]) || !$this->int2Op($_))
$unsetme = true;
break;
case self::CR_BOOLEAN:
case self::CR_FLAG:
$_ = $_crs[$i];
if (!$this->int2Bool($_))
$unsetme = true;
break;
case self::CR_ENUM:
case self::CR_STAFFFLAG:
if (!Util::checkNumeric($_crs[$i], NUM_CAST_INT))
$unsetme = true;
break;
}
if ($_crs[$i] === '' || $_crv[$i] === '')
trigger_error('Filter::evalCriteria - received malformed criterium ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
else
trigger_error('Filter::evalCriteria - received unhandled criterium: '.$_cr[$i], E_USER_NOTICE);
unset($_cr[$i], $_crs[$i], $_crv[$i]);
$this->error =
$this->shouldReload = true;
continue;
}
if (!$unsetme && intval($_cr[$i]) && $_crs[$i] !== '' && $_crv[$i] !== '')
continue;
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$_cr[$i]], 4, null);
unset($_cr[$i]);
unset($_crs[$i]);
unset($_crv[$i]);
// conduct filter specific checks & casts here
switch ($crType)
{
case self::CR_NUMERIC:
$_ = $_crs[$i];
if (Util::checkNumeric($_crv[$i], $param1) && $this->int2Op($_))
continue 2;
break;
case self::CR_BOOLEAN:
case self::CR_FLAG:
$_ = $_crs[$i];
if ($this->int2Bool($_))
continue 2;
break;
case self::CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && Util::checkNumeric($_crs[$i], NUM_CAST_INT))
continue 2;
break;
case self::CR_ENUM:
if (Util::checkNumeric($_crs[$i], NUM_CAST_INT) && (
(!$param2 && isset(static::$enums[$_cr[$i]][$_crs[$i]])) ||
($param2 && in_array($_crs[$i], static::$enums[$_cr[$i]])) ||
($param1 && ($_crs[$i] == self::ENUM_ANY || $_crs[$i] == self::ENUM_NONE))
))
continue 2;
break;
case self::CR_STRING:
case self::CR_CALLBACK:
case self::CR_NYI_PH:
continue 2;
default:
trigger_error('Filter::evalCriteria - unknown criteria type: '.$crType, E_USER_WARNING);
break;
}
trigger_error('Filter::setCriteria - generic check failed ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
$this->error = true;
trigger_error('Filter::evalCriteria - generic check failed ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
unset($_cr[$i], $_crs[$i], $_crv[$i]);
$this->error =
$this->shouldReload = true;
}
$this->fiSetCriteria = array(
'cr' => $_cr,
'crs' => $_crs,
'crv' => $_crv
);
$this->fiSetCriteria = [$_cr, $_crs, $_crv];
}
public function evalWeights() : void
private function evalWeights() : void
{
// both empty: not in use; not an error
if (!$this->values['wt'] && !$this->values['wtv'])
if (empty($this->values['wt']) && empty($this->values['wtv']))
return;
// one empty: erroneous manual input?
if (!$this->values['wt'] || !$this->values['wtv'])
{
unset($this->values['wt']);
unset($this->values['wtv']);
trigger_error('Filter::setWeights - one of wt, wtv is missing', E_USER_NOTICE);
$this->error = true;
unset($this->values['wt'], $this->values['wtv']);
$this->error =
$this->shouldReload = true;
return;
}
@@ -421,7 +478,8 @@ abstract class Filter
if ($nwt != $nwtv)
{
trigger_error('Filter::setWeights - wt, wtv are imbalanced', E_USER_NOTICE);
$this->error = true;
$this->error =
$this->shouldReload = true;
}
if ($nwt > $nwtv)
@@ -452,20 +510,18 @@ abstract class Filter
if (!Util::checkNumeric($val, NUM_CAST_INT))
return false;
foreach ($valid as $k => $v)
if (in_array($val, $valid))
return true;
foreach ($valid as $v)
{
if (gettype($v) != 'array')
continue;
if ($this->checkInput(self::V_RANGE, $v, $val, true))
return true;
unset($valid[$k]);
}
if (in_array($val, $valid))
return true;
break;
case self::V_RANGE:
if (Util::checkNumeric($val, NUM_CAST_INT) && $val >= $valid[0] && $val <= $valid[1])
@@ -486,7 +542,7 @@ abstract class Filter
if (!$recursive)
{
trigger_error('Filter::checkInput - check failed [type: '.$type.' valid: '.((string)$valid).' val: '.((string)$val).']', E_USER_NOTICE);
trigger_error('Filter::checkInput - check failed [type: '.$type.' valid: '.Util::toString($valid).' val: '.((string)$val).']', E_USER_NOTICE);
$this->error = true;
}
@@ -651,83 +707,72 @@ abstract class Filter
return null;
}
private function genericCriterion(int $cr, int $crs, string $crv) : ?array
{
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$cr], 4, null);
$result = null;
switch ($crType)
{
case self::CR_NUMERIC:
$result = $this->genericNumeric($colOrFn, $crv, $crs, $param1);
break;
case self::CR_FLAG:
$result = $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2);
break;
case self::CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && $crs > 0)
$result = $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true);
break;
case self::CR_BOOLEAN:
$result = $this->genericBoolean($colOrFn, $crs, !empty($param1));
break;
case self::CR_STRING:
$result = $this->genericString($colOrFn, $crv, $param1);
break;
case self::CR_ENUM:
if (!$param2 && isset(static::$enums[$cr][$crs]))
$result = $this->genericEnum($colOrFn, static::$enums[$cr][$crs]);
if ($param2 && in_array($crs, static::$enums[$cr]))
$result = $this->genericEnum($colOrFn, $crs);
else if ($param1 && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
$result = $this->genericEnum($colOrFn, $crs);
break;
case self::CR_CALLBACK:
$result = $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2);
break;
case self::CR_NYI_PH: // do not limit with not implemented filters
if (is_int($param1))
return [$param1];
// for nonsensical values; compare against 0
if ($this->int2Op($crs) && Util::checkNumeric($crv))
{
if ($crs == '=')
$crs = '==';
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
}
else
return [0];
}
if ($result && $crType == self::CR_NUMERIC && !empty($param2))
$this->fiExtraCols[] = $cr;
return $result;
}
/***********************************/
/* create conditions from */
/* non-generic values and criteria */
/***********************************/
protected function createSQLForCriterium(int &$cr, int &$crs, string &$crv) : array
protected function createSQLForCriterium(int $cr, int $crs, string $crv) : array
{
if (!static::$genericFilter) // criteria not in use - no error
return [];
if (isset(static::$genericFilter[$cr]))
if ($genCr = $this->genericCriterion($cr, $crs, $crv))
return $genCr;
[$crType, $colOrFn, $param1, $param2] = array_pad(static::$genericFilter[$cr], 4, null);
trigger_error('Filter::createSQLForCriterium - received unhandled criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_NOTICE);
$this->error = true;
$handleEnum = function(int $cr, int $crs, string $col, ?bool $hasAnyNone, ?bool $crsAsVal) : ?array
{
if ($hasAnyNone && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
return $this->genericEnum($col, $crs);
else if (!$crsAsVal && isset(static::$enums[$cr][$crs]))
return $this->genericEnum($col, static::$enums[$cr][$crs]);
else if ($crsAsVal && in_array($crs, static::$enums[$cr]))
return $this->genericEnum($col, $crs);
unset($cr, $crs, $crv);
return null;
};
return [];
$handleNYIPH = function(int $crs, string $crv, ?int $forceResult) : ?array
{
if (is_int($forceResult))
return [$forceResult];
// for nonsensical values; compare against 0
if ($this->int2Op($crs) && Util::checkNumeric($crv))
{
if ($crs == '=')
$crs = '==';
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
}
else
return [0];
};
$result = match ($crType)
{
self::CR_NUMERIC => $this->genericNumeric($colOrFn, $crv, $crs, $param1),
self::CR_FLAG => $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2),
self::CR_STAFFFLAG => $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true),
self::CR_BOOLEAN => $this->genericBoolean($colOrFn, $crs, !empty($param1)),
self::CR_STRING => $this->genericString($colOrFn, $crv, $param1),
self::CR_CALLBACK => $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2),
self::CR_ENUM => $handleEnum($cr, $crs, $colOrFn, $param1, $param2),
self::CR_NYI_PH => $handleNYIPH($crs, $crv, $param1),
default => null
};
if (!$result)
{
// this really should not have happened. The relevant checks are run on __construct()
trigger_error('Filter::createSQLForCriterium - failed to resolve criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_WARNING);
return [];
}
if ($crType == self::CR_NUMERIC && !empty($param2))
$this->fiExtraCols[] = $cr;
return $result;
}
abstract protected function createSQLForValues() : array;

View File

@@ -15,12 +15,12 @@ class IconElement
private const CREATE_ICON_TPL = "\$WH.ge('%s%d').appendChild(%s.createIcon(%s));\n";
private int $idx = 0;
private string $href = '';
private bool $noIcon = false;
public readonly string $quality;
public readonly ?string $align;
public readonly ?string $href;
public readonly int $size;
public readonly bool $noIcon;
public function __construct(
public readonly int $type,
@@ -70,6 +70,8 @@ class IconElement
if ($link || $url)
$this->href = $url ?: '?'.Type::getFileString($this->type).'='.$this->typeId;
else
$this->href = null;
// see Spell/Tools having icon container but no actual icon and having to be inline with other IconElements
$this->noIcon = !$typeId || !Type::hasIcon($type);
@@ -102,7 +104,7 @@ class IconElement
}
if ($this->href)
($a = $dom->createElement('a', $this->text))->setAttribute('href', $this->href);
($a = $dom->createElement('a', htmlentities($this->text)))->setAttribute('href', $this->href);
else
$a = $dom->createTextNode($this->text);
@@ -148,9 +150,9 @@ class IconElement
$params = [$this->typeId, $this->size];
if ($this->num || $this->qty)
$params[] = is_numeric($this->num) ? $this->num : "'".$this->num."'";
$params[] = is_int($this->num) ? $this->num : "'".$this->num."'";
if ($this->qty)
$params[] = is_numeric($this->qty) ? $this->qty : "'".$this->qty."'";
$params[] = is_int($this->qty) ? $this->qty : "'".$this->qty."'";
return str_repeat(' ', $lpad) . sprintf(self::CREATE_ICON_TPL, $this->element, $this->idx, Type::getJSGlobalString($this->type), implode(', ', $params));
}

View File

@@ -8,7 +8,7 @@ if (!defined('AOWOW_REVISION'))
class InfoboxMarkup extends Markup
{
public function __construct(private array $items, array $opts, string $parent = '')
public function __construct(private array $items, array $opts, string $parent = '', private int $completionRowType = 0)
{
parent::__construct('', $opts, $parent);
}
@@ -23,27 +23,48 @@ class InfoboxMarkup extends Markup
public function append(string $text) : self
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
if ($_ = $this->prepare())
$this->replace($_);
return parent::append($text);
}
public function __toString() : string
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
// inject before output to avoid adding it to cache
if ($this->completionRowType && User::getCharacters())
$this->items[] = [Lang::profiler('completion') . '[span class="compact-completion-display"][/span]', ['style' => 'display:none']];
if ($_ = $this->prepare())
$this->replace($_);
return parent::__toString();
}
public function getJsGlobals() : array
{
if ($this->items && !$this->__text)
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
if ($_ = $this->prepare())
$this->replace($_);
return parent::getJsGlobals();
}
private function prepare() : string
{
if (!$this->items || $this->__text)
return '';
$buff = '';
foreach ($this->items as $row)
{
if (is_array($row))
$buff .= '[li'.Util::nodeAttributes($row[1]).']' . $row[0] . '[/li]';
else if (is_string($row))
$buff .= '[li]' . $row . '[/li]';
}
return $buff ? '[ul]'.$buff.'[/ul]' : '';
}
}
?>

View File

@@ -146,9 +146,9 @@ class Listview implements \JsonSerializable
$this->tabs = $tabVar;
}
public function setError() : void
public function setError(bool $enable) : void
{
$this->_errors = 1;
$this->_errors = $enable ? 1 : null;
}
public function jsonSerialize() : array
@@ -164,10 +164,11 @@ class Listview implements \JsonSerializable
public function __toString() : string
{
$addIn = '';
if ($this->__addIn)
include($this->__addIn);
$addIn = file_get_contents($this->__addIn).PHP_EOL;
return "new Listview(".Util::toJSON($this).");\n";
return $addIn.'new Listview('.Util::toJSON($this).');'.PHP_EOL;
}
}

View File

@@ -19,8 +19,11 @@ class Tooltip implements \JsonSerializable
private ?string $buff = null;
private ?array $buffspells = null;
public function __construct(private string $__powerTpl, private string $__subject, array $opts = [])
public function __construct(private string $__powerTpl, private int|string $__subject, array $opts = [])
{
if (!is_int($this->__subject))
$this->__subject = Util::toJSON($this->__subject, JSON_UNESCAPED_UNICODE);
foreach ($opts as $k => $v)
{
if (property_exists($this, $k))
@@ -54,7 +57,7 @@ class Tooltip implements \JsonSerializable
public function __toString() : string
{
return sprintf($this->__powerTpl, Util::toJSON($this->__subject, JSON_AOWOW_POWER), Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n";
return sprintf($this->__powerTpl, $this->__subject, Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n";
}
}

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
die('illegal access');
class LocString
class LocString implements \JsonSerializable
{
private \WeakMap $store;
@@ -24,6 +24,11 @@ class LocString
$this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? '');
}
public function jsonSerialize() : string
{
return $this->__toString();
}
public function __toString() : string
{
if ($str = $this->store[Lang::getLocale()])

View File

@@ -30,6 +30,8 @@ class PageTemplate
private string $gStaticUrl;
private string $gHost;
private string $gServerTime;
private string $gUser;
private string $gFavorites;
private ?string $analyticsTag = null;
private bool $consentFooter = false;
private string $dbProfiles = '';
@@ -336,7 +338,7 @@ class PageTemplate
$result[] = "var fi_type = '".$this->filter->fiType."'";
if ($this->filter->fiSetCriteria) // arr:criteria, arr:signs, arr:values
$result[] = 'fi_setCriteria('.mb_substr(Util::toJSON(array_values($this->filter->fiSetCriteria)), 1, -1).");";
$result[] = 'fi_setCriteria('.mb_substr(Util::toJSON($this->filter->fiSetCriteria), 1, -1).");";
/*
nt: don't try to match provided weights on predefined weight sets (preselects preset from opt list and ..?)
@@ -470,7 +472,7 @@ class PageTemplate
private function update() : void
{
// analytics + consent
if (!isset($_COOKIE['consent']))
if ($this->analyticsTag && !isset($_COOKIE['consent']))
{
$this->addScript(SC_CSS_FILE, 'css/consent.css');
$this->addScript(SC_JS_FILE, 'js/consent.js');
@@ -484,6 +486,9 @@ class PageTemplate
// js + css
$this->prepareScripts();
$this->gUser = Util::toJSON(User::getUserGlobal());
$this->gFavorites = Util::toJSON(User::getFavorites());
// db profiling
if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
$this->dbProfiles = \Aowow\DB::getProfiles();
@@ -496,7 +501,7 @@ class PageTemplate
foreach ($this->lvTabs->iterate() as $lv)
if ($lv instanceof \Aowow\Listview)
$lv->setError();
$lv->setError(true);
}
// pre-serialization: if a var is relevant it was stored in $rawData
@@ -505,6 +510,11 @@ class PageTemplate
$this->context = null; // unlink from TemplateResponse
$this->pageData = []; // clear modified data
if ($this->lvTabs) // do not store lvErrors in cache
foreach ($this->lvTabs->iterate() as $lv)
if ($lv instanceof \Aowow\Listview)
$lv->setError(false);
$vars = [];
foreach ($this as $k => $_)
$vars[] = $k;
@@ -536,7 +546,7 @@ class PageTemplate
if (!$this->context)
return null;
if (!property_exists($this->context, $var))
if (!isset(get_object_vars($this->context)[$var]))
return null;
$this->rawData[$var] = $this->context->$var;

Some files were not shown because too many files have changed in this diff Show More