266 Commits
v1.1 ... v1.2

Author SHA1 Message Date
Sarjuuk
b890d6504e Guides: initial implementation
* a guide is a wrapper around an article providing management tools.
 * administration is limited to the review process. Needs to be expanded.
 * articles on DB pages are seperate. Editor will be added in the future.
2022-03-22 15:43:39 +01:00
Sarjuuk
33a870ef78 Structure/Types
* move distributed constant values to object
 * move reused iterators and checks to object
2022-03-22 15:05:50 +01:00
Sarjuuk
e109a6deed Siteconfig/Email
* make hardcoded email configurable
2022-03-22 14:59:45 +01:00
Sarjuuk
feb6ee25dd Misc/Fixups
* lost changes
2022-03-22 14:59:13 +01:00
Sarjuuk
a8edf6c912 POST/GET
* unify accessing &_GET and &_POST data
 * properly calc and display diffTime
2022-03-22 14:55:43 +01:00
Sarjuuk
3cb02f2204 JS/CSS
* unify handling
2022-03-22 14:45:55 +01:00
Sarjuuk
4b32811424 Tooltips/Errors
* do not send 404 header for xml or tooltips
2022-03-18 17:28:39 +01:00
Sarjuuk
4972cc0faf POST/GET
* unify accessing &_GET and &_POST data
2022-03-17 13:47:48 +01:00
Sarjuuk
e092a69175 JS/Misc
* grid tables no longer clear floating elements (have an eye out if this broke something)
 * add 1px to inner tooltips so it doesn't unexpectedly line break
2022-03-11 15:13:29 +01:00
Sarjuuk
1c7acf6a08 CommunityContent/Misc
* granularly define inclusion of comments, screenshots or videos
 * i hope everyone is on a 2y old php version by now
2022-03-10 14:09:38 +01:00
Sarjuuk
012ebe578b Items/Filter
* apply stats and name suffix to result set for filter #124 - random enchantment
 * fix filter #125 - required arena team rating
 * fix filter #85 - objective of quest
2022-03-06 09:52:19 +01:00
Sarjuuk
e125cab690 Search/Spells
* do not search for npc abilities under misc. spells tab
2022-03-04 10:27:19 +01:00
Sarjuuk
a1e7bfaa02 Quest/Detailpage
* fixed breadcrumb trail for quests categorized to subzones
 * display auto accept status in infobox
2022-03-04 10:01:30 +01:00
Sarjuuk
73c1118601 Setup/Update
* reapply flags for community content when rebuilding DB
   fixes associated filters and meta page 'missing screenshots'
2022-03-03 19:07:10 +01:00
Sarjuuk
33ec14f232 Merge remote-tracking branch 'github/master' into ghMaster 2022-02-28 21:03:36 +01:00
Sarjuuk
366f68f24f Setup/Misc
* squash some more php warnings during setup
 * automaticly sync if required by updates
2022-02-28 21:01:07 +01:00
Sarjuuk
91f8fe8925 Setup/Mails
* add lost world db dependencies to script
2022-02-28 12:52:09 +01:00
Sarjuuk
8431329645 SmartAI/Spells
* fixed fetching casters for spellId (as seen on 'used by' tabs)
2022-02-28 09:11:22 +01:00
Sarjuuk
19584feab0 Setup/Itemset
* fixed missing items in set
 * fixed php warnings during setup
 * fixed triggering scripts when changing config values
2022-02-27 23:53:36 +01:00
Sarjuuk
bc71ae762c Setup/Data
* move custom data from setup scripts to DB
 * move strings from DB to locale files
 * use common function to calculate coordinates of dungeon entrances instead of inline sql
 * fixed manual data zones and itemset
 * fixed typos across the board
2022-02-21 23:31:14 +01:00
Sarjuuk
10805a1f70 Profiler/Regions
* add Asia regions
 * fixed some typos
2022-02-20 22:47:54 +01:00
Sarjuuk
a24e8594fb Profiler/Localization
* support non-latin realm names for guilds and arena teams also
2022-02-16 04:17:40 +01:00
Sarjuuk
01a9744ba7 Locks
* reworked how an dwhen locks on GameObjects and Items are displayed
 * added structure for LockType.dbc
2022-02-14 23:17:51 +01:00
Sarjuuk
53e2af2116 Profiler/Localization
* support non-latin realm names
 * actually set characters as updated after resync was skipped due to character not having logged in since last resync
2022-02-14 16:04:57 +01:00
Metalaka
4255933328 Creature/Loot
* display skill condition on drop (#325)
2022-02-13 23:44:36 +01:00
Sarjuuk
1856f0ef8f README/QA
* added another repeat gotcha to the Q+A section
2022-02-13 23:43:12 +01:00
Sarjuuk
731f648cd2 Setup/Misc
* wrap db test in try/catch block
 * escape more mysql8 keywords
2022-02-12 15:22:52 +01:00
Sarjuuk
6a2511866e Conditions/Localization
* fixed index for descriptions
 thx @Faq for noticing
2022-02-12 14:37:32 +01:00
Sarjuuk
b777262670 Merge branch 'master' of github.com:Sarjuuk/aowow 2022-02-12 14:27:10 +01:00
Sarjuuk
c950cdc757 Setup/Misc
* fixed variable name: setup parameter --locales should work again.
 * escaped GROUPS as keyword in mysql8
2022-02-12 14:21:07 +01:00
Sarjuuk
541224f87b Markup/Misc
* fixed ajax fulltext sanitizer eating \s instead of \n after 57864d2544
 * happy new year
2022-01-06 14:21:51 +01:00
Sarjuuk
dcb8995b1a Markup
* allow 'auto' as width for tables
 * db tag 'money' may include currency AND items
2022-01-05 23:32:06 +01:00
Sarjuuk
2ec4809c7f Spells/Reagents
* try to fix wrong reagent amount in cases where one reagent requires multiples of itself and is triggered by an item
2022-01-05 23:27:44 +01:00
Sarjuuk
061449686d Revert "[WIP] Conditions"
This reverts commit a1f0f3d30d.
2021-11-21 09:50:59 +01:00
Sarjuuk
f3f8dacef9 Profiler/Talents (#315)
* fix usage of active talent spec
2021-11-19 20:30:16 +01:00
Sarjuuk
a1f0f3d30d [WIP] Conditions
* add missing conditionsTypes

todo:
 process params in Util::getServerConditions()
 add locale strings to js
 extend verbose display in global.js ConditionList
2021-11-04 22:49:12 +01:00
Sarjuuk
e4b8a8e908 Zone/Mapper
* show fishing nodes on map
2021-11-04 22:40:07 +01:00
Sarjuuk
41f415095a JS/Tooltips
* define tooltip_noequipcooldown
2021-11-04 22:39:58 +01:00
Sarjuuk
b824bc54c2 ItemList/ExtendedCost
* restore internal list pointer after using iterator get extended costs
2021-11-04 22:39:07 +01:00
Sarjuuk
35aca18346 JS/Markup
* enable "event" and "currency" codes for regular users
2021-11-04 22:38:55 +01:00
Sarjuuk
4b37a4fd70 CreatureText/Formating
* removed excess escape on talker name
2021-11-04 22:38:43 +01:00
Sarjuuk
c665062faa Staff/vehicles
* fixed error when trying to lookup accessory creatures in world db with aowow guids
2021-11-04 22:38:30 +01:00
Sarjuuk
fca627c408 * fixed bitshift by negative number issue when evaluating spell MiscValues
* defined upper limit for localeIds and also discard negative localeIds
* fixed eval error when parsing spell descriptions. Calculated numbers sadly can't be localized.
2021-11-04 22:37:15 +01:00
Sarjuuk
ea25776225 Zone/Map
* display mailboxes on zone map
2021-11-04 22:36:26 +01:00
Giacomo Pozzoni
5781dfb69e Fix syntax error (#312)
Fix wrong bracket order revealed by PHP 8
2021-10-27 19:52:15 +02:00
Sarjuuk
60e3f62504 Update README.md
Added two more 'gotchas' to the README.md
2021-10-26 18:53:53 +02:00
Sarjuuk
9cda84ef88 added but report template 2021-10-26 10:57:57 +02:00
Sarjuuk
3495a7a40d Achievememnt/Series
* keep series zero indexed like quests
2021-10-24 21:29:33 +02:00
Sarjuuk
8acc93c325 Search/Tabs
* switch between single and multi-class display as necessary
2021-10-24 21:20:46 +02:00
Sarjuuk
4755058fd0 Merge branch 'master' of https://github.com/Sarjuuk/aowow 2021-10-24 20:59:47 +02:00
Sarjuuk
306f8eb9b6 Locale/enUS
* addition to 5fe9c23be2
2021-10-24 20:59:25 +02:00
wodim
5fe9c23be2 Fix category name in enUS (#239) 2021-10-24 20:58:38 +02:00
Sarjuuk
68e262d96c Locale/zhCN
* analogue to 8727d4f66f
2021-10-24 20:55:33 +02:00
Dima
8727d4f66f Update locale_ruru.js (#291)
missing space
2021-10-24 20:55:02 +02:00
Sarjuuk
07b9ed4275 Setup/CLI
* also fix empty --build param building all files
 * fixed copy/paste typo
2021-10-24 20:41:44 +02:00
Giacomo Pozzoni
cb2274bbab Fix php aowow --sql not doing anything (#307) 2021-10-24 20:40:50 +02:00
Stefano Borzì
7c293c405f remove unused static method getName() in SoundList, close #293 (#294) 2021-10-24 20:30:14 +02:00
Sarjuuk
6819c1b1f2 Misc
* fixed misc php8 errors that went unnoticed as php7 warnings before
 * added lost areatrigger generator to Setup script
 * updated readme with working TCDB info and php 8.0 support
2021-10-24 19:47:45 +02:00
Sarjuuk
57864d2544 PHP8/AjaxHandler
* filter callback function must be defined static
 * fixed some typos
 * fixed commentbody filter callback eating \n chars
2021-10-24 19:23:53 +02:00
Sarjuuk
f9ed75d5af SmartAI/Spells
* fixed fetching spells from SmartAI for Abilities tab

Co-authored-by: jackpoz <giacomopoz@gmail.com>
2021-10-24 18:12:53 +02:00
jackpoz
453690c575 Fix error on windows when no parameters are passed in query string 2021-10-24 17:35:24 +02:00
Sarjuuk
557638b3fe PHP8/Misc
* reduce redundancy after merge
2021-10-24 17:33:47 +02:00
jackpoz
f9584f09b5 Handle is_callable returning false with instance methods on static types since PHP 8
See https://php.watch/versions/8.0/non-static-static-call-fatal-error
2021-10-24 17:15:26 +02:00
Sarjuuk
6b49aa6069 Setup/Misc
* fix some outdated array indizes during setup
 * fix indexing when connecting to DB for first time
 * add initial loading of config strings during setup
2021-10-24 17:12:05 +02:00
Sarjuuk
56e70e22bb Item/DetailPage
* fixed infobox cost for items with buycount > 1
2021-03-14 16:31:35 +01:00
Sarjuuk
6f69144498 Merge remote-tracking branch 'github/master' into ghMaster 2021-03-14 16:13:39 +01:00
Sarjuuk
aa82655845 SmarAI/Lookups
* unify lookups in functional groups
 * move code to class SmartAI
2021-03-14 02:04:56 +01:00
Sarjuuk
c456ade870 Skills/Breakpoints
* display skill learning breakpoints for gathering skills
 * fixed mixed up mining-skinning and savanging-skinning filters for NPCs
 * defined some magic skill numbers
2021-03-14 02:04:43 +01:00
Dima
aa70ea0323 Localization (#284)
use multibyte function to manipulate localized text.
2021-03-02 20:24:11 +01:00
Sarjuuk
fbc5d43aab Setup/Maintenance
* toggle maintenance mode in a more reasonable manner (i.e. not when displaying help test)
 * move connectivity tests to class DB
 * restore generate everything functionality when running --sql and --build with an empty parameter set
2021-02-20 20:35:53 +01:00
Sarjuuk
43778b01e7 Setup/DB
* connect to db after successful setup so siteconfig doesn't fail
2021-02-18 22:31:30 +01:00
Sarjuuk
f5701e7979 Misc/Config
* add config values lost from dd63a6a2ab
 * update TCDB compatibility info
 * update current year
2021-02-18 19:00:55 +01:00
Dima
33c3132346 Localization/ruRU (#281)
* add reputation strings
2021-02-18 13:36:29 +01:00
Sarjuuk
d6ca3d70cf Listview/Percent
* use same logic for display and sorting
2021-02-15 20:07:49 +01:00
Giacomo Pozzoni
392b5bbdda CLI/Typos (#272)
* fixed typos in log
2021-02-15 19:48:35 +01:00
Sarjuuk
e810554695 Mapper/Instances
* do not display unmapped instances (3.3.5 from data) by default
2021-02-15 19:30:48 +01:00
Sarjuuk
8313bb4194 Icons/DetailPage
* don't cast name to ucFirst
2021-02-15 19:16:10 +01:00
Sarjuuk
0c1b73d6ac Icons/Names
* icon names can contain spaces. So indiscriminate trimming should be discouraged.
 * ...
 * unrelated stuff: please don't break...
2021-02-15 19:08:56 +01:00
Sarjuuk
7a74c36448 Setup/Profiler exclusions
* set time limit to generation of profiler exclusions file
 * remove some unused logging
2021-02-15 18:20:01 +01:00
Sarjuuk
23abce304c Readme/Setup
* use more intuitive setup command. "firstrun" is still available but usage is discouraged.
2021-02-15 17:59:36 +01:00
Sarjuuk
e5e62e2936 Merge remote-tracking branch 'github/master' into ghMaster 2021-02-15 17:55:24 +01:00
Dima
c0a1e393b0 Localization/ruRU (#278)
* fixed typo in user contribution template
2021-02-15 17:32:46 +01:00
Dima
898a4fd8a4 Localization/Typo (#279)
* fixed icons path in ruRU and frFR menus
2021-02-15 17:13:10 +01:00
Dima
3978169bf6 Localization/ruRU (#280)
* added missing zone and gameobject strings
2021-02-15 17:10:22 +01:00
Dima
01e89db491 Setup/Help (#276)
* fixed log command instructions
2021-02-15 16:50:25 +01:00
Sarjuuk
59f58f8506 Setup
* automatically set and unset maintenance mode when data is being edited (either sql or files)
 * added more verbose help prompts. They should now always appear when -h is used.
 * added --setup as more intuitive command for --firstrun. --firstrun is now considered deprecated and will be removed in due time.
 * unfuck order of indizes in --siteconfig
 * fixed some typos
 * convert echo use to CLI::write
 * move scattered command line argument checks to CLISetup
2020-12-30 00:29:30 +01:00
Sarjuuk
c65bd88867 Setup/RandPropPoints
* refer to correct dbc file to get item level scaling for random enchantments
2020-12-29 13:20:42 +01:00
Sarjuuk
2eaed47ba4 Items/Misc
* fixed queries erronously using ucFirst column names
 * fixed visible html in infobox
 * added forgotten string for locale 3
2020-12-28 23:27:49 +01:00
Sarjuuk
888ff28121 Item/DetailPage
* alwaya display item level of gems regardless if cut or not
 * fixed similar item search result for items without class restrictions
2020-12-27 20:45:44 +01:00
Sarjuuk
baf47433dd Enchantment/DetailPage
* removed tooltip-highlight when there is no tooltip to display
Spells/Tooltips
 * fixed ancient copy-paste error and 'greater than or equal' conditions are now always evaluated as such
2020-12-27 20:07:57 +01:00
Sarjuuk
89275a0e71 Readme
* remove reference to expired branch
2020-12-19 01:06:30 +01:00
Sarjuuk
15fb7b8711 NPCs/DetailPage
* display difficulty versions of the creature in the infobox
2020-12-19 00:18:46 +01:00
Sarjuuk
467a31fa3b Template/Escaped Strings
* escape creature subnames in DetailPage
 * escape creature names & subnames in Tooltips
 * js escape inherited filter froms
2020-12-19 00:16:09 +01:00
Sarjuuk
5b414500a7 Creature/DetailPage
* display school resistance under stats in infobox
2020-12-19 00:00:49 +01:00
Sarjuuk
cac57f5cd8 Spell/DetailPage
* fixed recursing SmartAI ActionLists for used-by relations
2020-12-19 00:00:49 +01:00
Sarjuuk
3188a7b55f Template/generic detail page
* switch position of article and extra text section
 * pray the layout doesn't break somewhere unexpected
2020-12-19 00:00:49 +01:00
Sarjuuk
4ed2d0836f Localization/Invisibility
* set placeholders for unknown but used invisibilityTypes to suppress errors on access
2020-12-17 22:25:25 +01:00
Sarjuuk
be77474637 Misc/Optimizations
* general minor cleanups
 * do not sync characters that haven't logged in since last resync.
2020-12-17 22:24:42 +01:00
Sarjuuk
82d3a8508d Localization/Placeholders
* apply changes from d070b303b4 to placeholder texts
2020-10-25 17:56:12 +01:00
wodim
d070b303b4 Localization (#237)
* removed excess hyphens
2020-10-25 17:49:26 +01:00
wodim
7062ff74a4 Localization/Typo (#226)
* Fix typo
2020-10-25 17:41:53 +01:00
Sarjuuk
df1f29c986 LinkedSpells/Localization
* Smoothed out word choice. Should be more clear now.
2020-10-25 17:27:24 +01:00
Sarjuuk
178c13ec72 SmartAI/SpellCasts
* fixed getting spell casts per NPC
 * fixed spell usage for NPCs when used by Timed Actionlists
 * added 'used by (smart) areatrigger' to SpellDetailPage
 * fixed forgotten rename from SmartAI overhaul
2020-10-25 17:09:05 +01:00
Sarjuuk
e2fe765980 SAI/Compat
* add changes from TC of the last three years
* reduce redundancy when handling creature text
* reserve more space in target, event, action parameters. Move own parameters further back to avoid confict with future param expansion by TC
2020-10-25 15:47:23 +01:00
Sarjuuk
003ac1c931 Listviews/Quests
* fixed displaying as many quest requirement columns as items requiring quests
2020-10-10 11:08:04 +02:00
Sarjuuk
0430d75989 Tooltips/Cleanup
* removed double escaped json
 * fixed itemset tooltips with classes
 * set cookies to samesite to avoid being rejected by the browser
2020-10-10 11:00:59 +02:00
Sarjuuk
59fb6c1bc9 DetailPage/GameObject
* display related faction in infobox
2020-10-10 10:14:13 +02:00
Sarjuuk
12c5f6f468 Setup/Maps
* fixed small map height (small maps used in tooltips)
 * map files need to be regenerated manually

thx @wodim
2020-09-16 21:29:04 +02:00
Sarjuuk
c92ab37de1 Misc/Typo
* fixed typo in SAI localization

Co-authored-by: wodim <neikokz@gmail.com>
2020-09-16 20:08:13 +02:00
Sarjuuk
88417cd5a6 Setup/Spawns
* added required areatable.dbc to spawns setup script, forgotten in 62acd541b2
2020-09-11 23:08:03 +02:00
Sarjuuk
583f8658d7 Pages/Caching
* use native functions of ReflectionProperty instead of magic numbers. GetModifiers() may return different values then expected/documented
2020-09-02 21:17:05 +02:00
Sarjuuk
04a14393e5 DB/MySQL
* fixed some more use cases of 'rank'
2020-09-02 21:12:00 +02:00
Giacomo Pozzoni
f34b4827ee DB/MySQL (#219)
* fixed usage of field name 'rank' which became a keyword in MySQL 8
2020-09-02 20:58:38 +02:00
Sarjuuk
aac9aab3c9 Spells/DetailPage
* diplay filtrable spell attributes in overview table
 * replace some magic numbers with constants
2020-08-31 18:21:53 +02:00
Sarjuuk
349c32d0a6 SAI/Targets
* add forgotten change from 243429bf68 to target tooltips
2020-08-30 18:28:28 +02:00
Sarjuuk
5179e6e09b SAI/Typo
* fixed typo in SmartAI event handler 76 bricking related object/npc pages
2020-08-30 18:04:57 +02:00
Sarjuuk
366a0f2040 Game/worldPosToZonePos
* catch DB errors resulting in wrong return type
2020-08-30 17:33:27 +02:00
Sarjuuk
93948a2ec5 Spells/DetailPage
* resolve spell effect #86 with info from 203573db83
2020-08-06 16:24:35 +02:00
Sarjuuk
3e24814518 README/Conf
* check for presence of lib GMP when trying to enable TC auth
 * also added missing requirement to README (thx @jackpoz for reminding me)
2020-08-03 22:11:05 +02:00
Sarjuuk
726fe90bb0 README
* hear ye hear ye! the regulations have changed!
 * while the master branch will now use SRP6 for TrinityCores auth, the branch sha1-auth will be available for about three months to ease migration.
 * until Nov. 2020 updates will be applied to both branches
2020-08-03 15:36:49 +02:00
Treeston
fb2ab8f613 SRP6 for user auth (#38)
* SRP6 for user auth, implementation of 3164b58c7d
2020-08-03 15:20:34 +02:00
Sarjuuk
72e950713a Achievement/Criteria
* use reference achievement for criteria if set
2020-07-25 21:53:23 +02:00
Sarjuuk
ce90229a8b Quest/Rewards
* implement changes to max level xp conversion from 670085d8c0
2020-07-25 21:04:36 +02:00
Sarjuuk
4bd32207db Setup/Updates
* skip unexpected characters in update fields of aowow_dbversion
2020-07-20 21:14:06 +02:00
Sarjuuk
844a4c0e52 Setup/Fixups
* added lost changes from
   321f28d35c and
   62acd541b2 to setup
2020-06-25 19:27:33 +02:00
Sarjuuk
d858c85465 Items/Tooltips
* display ppm in tooltip
2020-06-07 21:53:03 +02:00
Sarjuuk
82d3441b22 Spells/DetailPage
* expand used by - tabs to show spells from timed action lists and smart_script casts from gameobjects in general
2020-06-07 21:52:55 +02:00
Sarjuuk
c08773eb6f Screenshots/Misc
* no functional changes
2020-06-06 17:30:04 +02:00
Sarjuuk
240024fb10 Summary/Itemsets
* fixed set bonus calculation when using multiple sets in one summary

Localization
 * create more concise error messages when accessing nonexistant locale strings
2020-06-06 15:55:54 +02:00
Sarjuuk
321f28d35c Achievements/Category
* moved names from db to locale files
2020-06-04 00:56:59 +02:00
Amandil
8e14e4a9f3 fix signin url for locale_frfr (#208) 2020-06-03 20:32:19 +02:00
Sarjuuk
184e5e5c8d Setup/DB
* fixed input prompt broken after iputting aowow's info
2020-06-01 12:24:54 +02:00
Sarjuuk
7b9b542e65 Merge remote-tracking branch 'github/master' into ghMaster 2020-05-30 11:50:21 +02:00
Stefano Borzì
a3d35a7ad5 Update credits (#205)
* Update credits
2020-05-29 18:53:22 +02:00
Sarjuuk
366bdf54a6 User/Cookies
* additional fixes against announcements not staying closed
2020-05-28 19:37:54 +02:00
Sarjuuk
d22e90ca85 DB/Logs
* set up some query profiling
2020-05-28 18:43:24 +02:00
Sarjuuk
c290f845d6 Quest/DetailPage
* added tab and note with quests from the same pool if available

 * fixed announcements poping up after being dismissed previously
 * fixed double escaping of quest tooltips
2020-05-28 18:43:14 +02:00
Sarjuuk
8741c7479f Misc/Fixups
* vehicle accessories now get moved with their vehicle
 * try to account for multilevel zone offset nonsense
 * fixed some more typos and unaccounted null cases
2020-05-27 15:27:13 +02:00
Sarjuuk
62acd541b2 Maps/Spawns
* Entities (Objects, NPCs, ect) can now easily be assigned to a different map to be displayed on by clicking their pip on the map
 * Entities with already assigned area (by TrinityCore) that were unable to be matched onto the map are no longer discarded. They'll now show up in appropriate listviews.
 * Entities without already assigned area that are also unable to be matched onto the map now get an area assigned as long as the relationship areaId <=> mapId is unique (read instanced areas)
2020-05-26 21:11:15 +02:00
Sarjuuk
7db841b823 Misc/Typo
* fixed typo from 6cabfd3
2020-05-25 22:26:45 +02:00
Sarjuuk
6cabfd3864 Misc/Cleanup
* moving commonly used strings to defines
 * moving commonly reused/similar page generation functions to the parent
 * generally using consistent return types, more type hints and less strings
 * prevent browser context menu when right clicking on UI elements with their own context menus
 * fixed menu path for icons
2020-05-24 15:04:42 +02:00
Sarjuuk
b044488308 Profiler/Recipes
* _really_ fix learning starter recipes per skill attempted in 96bbe326a8
2020-05-18 20:23:23 +02:00
Sarjuuk
a1d3be86f7 Profiler/Talents
* fixed column reference changed in f8230a59a9
2020-05-09 16:57:45 +02:00
Sarjuuk
8378354f8b Creatures/Objects
* added some missing links between boss npcs and loot chests
 * use name of group encounters from dungeonencounter.dbc instead of single creature
Profler
 * remove link to blizzard arsenal
 * truncate long titles in raid progression tracker
2020-05-03 16:52:00 +02:00
Sarjuuk
08f8297629 Objects/SpellFocus
* added tab to DetailPage with spells requiring this object to be cast
2020-04-26 14:22:11 +02:00
Sarjuuk
9b4c0bb09e Zones/Misc
* hide some more unused zone
 * link zones Plaguelands: Scarlet Enclave and Onyxia's Lair to correct map
 * fixed wotlk raids all being heroic
 * link icecrown citadel sub maps to master and hide sub maps
2020-04-19 18:42:10 +02:00
Sarjuuk
92a1016a0f Spells/Misc
* display dbc definitions before articles on detail pages
 * fixed logic when displaying skill step numbers
 * made custom spell columns for linked spells and stack rules localizable
2020-04-19 16:51:34 +02:00
Sarjuuk
08c98bc8ee Search/Spells
* fixed some spells being skipped in search (hopefully)
2020-04-10 23:01:15 +02:00
Sarjuuk
b7bf58a664 Profiler/Excludes
* exclude some more unobtainable receipes
2020-04-10 12:35:53 +02:00
Sarjuuk
64ce4826c6 Profiler/Excludes
* tag some more receipes as unavailable
2020-04-05 15:14:49 +02:00
Sarjuuk
96bbe326a8 Profiler/Spells
* fixed applying auto-learned profession spells
2020-04-05 14:24:08 +02:00
Sarjuuk
0f9a3e8cb6 Profiler/Custom
* fixed deleted profiles counting towards the user cap of 10
2020-04-05 12:31:59 +02:00
Sarjuuk
9f8c643cf4 Filter/Criteria
fixed
 * Quest: class/race-specific
 * NPC: skinnable
2020-04-05 11:58:36 +02:00
Sarjuuk
c2a1556e8a Loot/Refloot
* fixed Reference Loot's drop chance counting towards the adaptive drop chance of loot within the same loot group.
2020-04-04 15:20:51 +02:00
Sarjuuk
15246ea964 Util/Time
* fixed time formatting for milliseconds
 * only process absolute time values

Co-authored-by: Helias <stefanoborzi32@gmail.com>
2020-04-01 22:29:36 +02:00
Sarjuuk
3b749025de Items/Itemsets
* fixed erronous linking to the baseline set for virtual sets
 * display items from the same set, for the same slot in the 'see also' tab
2020-03-28 15:56:24 +01:00
Sarjuuk
bf2e1bd612 Profiler/DB
* implement changes from https://github.com/TrinityCore/TrinityCore/commit/1925778e44a
 * thx @kep123 for pointing these out
2020-02-28 22:04:33 +01:00
Sarjuuk
301c29e944 Localization/Profiler
* added some more strings for loc 4 (zhCN)
 * thx @kep123 for providing those
2020-02-28 22:04:03 +01:00
Sarjuuk
54b20c3131 NPC/Spells
* always treat spells from creature_template_spell as controlled abilities
2020-02-26 17:29:33 +01:00
Sarjuuk
55d19589b8 Merge remote-tracking branch 'github/master' into ghMaster 2020-02-26 16:37:48 +01:00
Sarjuuk
59ef124a26 SmartAI
* fixed several ocurences of time strings being cast to int
 * always use short time format
 * also fixed that one questionmark typo that kicked this whole commit off
2020-02-26 16:33:31 +01:00
Sarjuuk
47458e3ec2 Spell/Effects
* also link back from creature to summon spell for spell effects 56 & 112
2020-02-24 23:33:01 +01:00
Giacomo Pozzoni
4d08fb974d Escape MySQL 8 keywords (#193) 2020-02-24 22:02:49 +01:00
Sarjuuk
3f1e44d3e2 Titles/DetailPage
* fix page titles for .. titles
 * escape htmlesque tags not in the template but individually as needed
2020-02-24 21:40:50 +01:00
Sarjuuk
aa66a7644b Areatrigger/Names
* fix unnamed areatrigger after fetching them from DB and not clientside
Mail/Names
 * fix unnamed mails after fetching them from DB and not clientside
2020-02-24 02:00:37 +01:00
Sarjuuk
3ff855afe8 Setup/Mails
* index can actually be negative
2020-02-24 01:36:42 +01:00
Sarjuuk
3c088cd4e8 Spell/Effects
* parse npcs for spell effect 56 and 112
2020-02-23 22:17:16 +01:00
Sarjuuk
0694367ee9 Spell/Effects
* parse triggered spell from effect 140 and 141
 * generally display non-zero effect values
2020-02-23 21:36:26 +01:00
Sarjuuk
4bda806b9b DB/Mails
* add lost column `attachment`
2020-02-23 20:32:40 +01:00
Sarjuuk
b37efc480a Item/Icons
* handle icons containing whitespaces
2020-02-23 19:46:08 +01:00
Sarjuuk
717e9c43be Objects/Names
* fix unnamed objects after fetching them from DB and not clientside
2020-02-23 19:13:43 +01:00
jackpoz
1f84cae1dd Sort zones in ?npc=... by spawn count 2020-02-23 19:01:06 +01:00
Sarjuuk
5bb15d47d7 Misc/Readme
* update required TDB-version (hint: its never 'latest')
 * fixed artifacts from setup conversion
 * version push
2020-02-23 18:45:44 +01:00
Sarjuuk
16bd59e0cc NPC/Trainer
* add support for https://github.com/TrinityCore/TrinityCore/commit/bf3ab6d9c43
 * fixed some bugs causing conditions to not be displayed
 * fixed some trainer not being displayed for most skills
2020-02-23 17:29:12 +01:00
Sarjuuk
d4c0c0535a Quest/Breadcrumbs
* implemented https://github.com/TrinityCore/TrinityCore/commit/5ed77113b63
 * fixed some engrish
 * fixed a typo in typecasting string searches
2020-02-23 14:14:34 +01:00
Sarjuuk
eaa982e5b3 Totems/Display
* implment https://github.com/TrinityCore/TrinityCore/commit/915f8a9d2c2
2020-02-23 12:33:09 +01:00
Sarjuuk
48552a97ab Creature/Spells
* update db structure for https://github.com/TrinityCore/TrinityCore/commit/d5fb0a30ec6
2020-02-14 17:35:44 +01:00
Sarjuuk
243429bf68 SmartAI
* update db structure for https://github.com/TrinityCore/TrinityCore/commit/7634a57f64a
2020-02-14 16:54:36 +01:00
Sarjuuk
bc18ca174c Spells
* update db structure for https://github.com/TrinityCore/TrinityCore/commit/8edea4a3c29
2020-02-14 16:39:06 +01:00
jackpoz
1c08e5d9cf PHP/v7.3
* fixed warnings when migrating to php v7.3
 * https://www.php.net/manual/en/migration73.incompatible.php#migration73.incompatible.core.continue-targeting-switch
2020-02-14 16:26:38 +01:00
Sarjuuk
d3c5011694 Achievements
* update db structure for https://github.com/TrinityCore/TrinityCore/commit/352c4a4c680
2020-02-14 16:22:24 +01:00
Sarjuuk
c77cd92609 * updated footer to currentYear™
* revision push
2020-02-14 16:16:40 +01:00
jackpoz
1ff81ab07c Add support to https://github.com/TrinityCore/TrinityCore/pull/24102 2020-02-14 16:15:34 +01:00
Sarjuuk
1c7660316d Items/Gems
* fixed indexing error when calculating item enchantments introduced in c7fe84b7e0
2020-02-14 16:15:34 +01:00
wodim
eeb39bb83f Better wording for NPC loot containers 2020-02-14 15:19:02 +01:00
wodim
f99ff8c4b8 Fix typo 2020-02-14 15:19:02 +01:00
Sarjuuk
cf129ca3ce Pages/Quest
* escape htmlesque tags from quest titles for Infobox
2020-02-14 09:25:13 +01:00
Sarjuuk
fd04e9f977 Implemented new type: mail
* display and link clientside mails to other types and events
 * fixed favorites menu for new types
 * fixed sorting column triggered spells in enchantment listview
 * some misc cleanups
2020-02-14 00:36:41 +01:00
Sarjuuk
ccef11323b Setup/Scripts
* restructure setup to allow for self contained setup steps to self register (just the sql for now)
 * should ease adding new scripts in future
2020-02-14 00:36:41 +01:00
Sarjuuk
163e3d82b0 Spells/DetailPage
* fixed rogue reference causing created items to display as a different item

 thx @Rushor for pointing in the right direction
2020-02-12 23:59:35 +01:00
Sarjuuk
fc0902d476 Profiler/CSS
* fixed some batch-replace fails
 * year++++
2019-07-30 23:17:56 +02:00
Sarjuuk
0fd2944d8b Spells/DK
* fixed setup flagging wrong spells as hidden
 * fixed menu structure for DK spells not requiring DK class
 * fixed type for rune cost in spell tooltip
2019-01-04 17:19:14 +01:00
Sarjuuk
7f36dc87cf Mails
* apply table quest_mail_sender to display correct mail source
 * fixed mails from mailtemplate.dbc for achievement rewards
2018-12-15 00:21:52 +01:00
Sarjuuk
34fe4c2654 Creatures/Rank
* set unused ranks to 0 during setup to fix display issues
2018-12-14 23:30:26 +01:00
Sarjuuk
392610b899 Icons
* add icon to infobox for db types with icon (*duh!*)
 * fixed a couple issues with markup icondb in the process
 * fixed wrong data display due to shared name fields on joined table
2018-12-14 23:09:32 +01:00
Sarjuuk
ead6e72668 Page/Currency
* display price column in "currency for" tab
2018-12-11 19:55:02 +01:00
Sarjuuk
db012cfa8c Page/Search
* dropped support of wildcard characters
 * search for literal usage of underscore
2018-12-07 22:09:52 +01:00
Sarjuuk
09176d1ae9 Profiler/Names
* handle duplicate names due to rename flags more gracefully
 * should fix infinite reload bug on newly renamed profies
2018-12-02 19:25:43 +01:00
Sarjuuk
972a7f241e Misc/Fixes
* cast GET-params to lower case
 * fixed typo in constant name
 * fixed scope issue when aggregating sql stats
2018-12-02 19:22:21 +01:00
Sarjuuk
da1946df0f Misc/Fixes
* resolve two additional spell effects (11, 44)
 * fixed overzealous find/replace in Loot::iterate()
 * fixed wrong placeholder for type 32 in Conditions System
2018-12-02 00:55:34 +01:00
Sarjuuk
cc5be5261c Setup/DBconfig
* remove nonfunctional placeholder file
 * its too confusing
2018-12-01 20:03:26 +01:00
Sarjuuk
af7b9f57b0 Page/Search
* always display tab to denote what type was found
2018-11-30 18:33:59 +01:00
Sarjuuk
d9cd24026c PHP/Core
* enforced v7.1 compliance, preparations for v7.2
 * removed deprecated usage of each()
 * prefer array deconstruction via [] instead of calling list()
 * try to catch failed session_start()
 * prefer ... - Token instead of calling func_get_args() func_num_args()
 * enforce return types in AjaxHandler
 * revision push
2018-11-29 00:45:19 +01:00
Sarjuuk
f8a34aa98e Account/Cookies
* fixed saving cookie-data to account
 * e.g. announcements shouldn't pop up again if you close them once
2018-11-27 00:01:06 +01:00
Sarjuuk
13db19c64f Spells/Models
* fixed assigning same display to multiple spells from the same SpellList
 * fixes missing mounts in Profiler
2018-11-26 23:44:46 +01:00
Sarjuuk
04209cfc6d PHP-Version
* return type declarations require php v7.0 or newer
 * updated README and and version check accordingly
2018-11-20 19:00:20 +01:00
Sarjuuk
470498f63c Page/Achievement
* catch fatal error for achievements without criteria
2018-11-18 16:17:11 +01:00
Sarjuuk
484944bfc0 SmartAI
* parse and verbosely display smartAI for creatures, gameobjects and areatrigger
2018-11-18 15:31:22 +01:00
Sarjuuk
8620cbcf20 Setup/Quests
* update structure for TrinityCore/TrinityCore@2f5403d4af (Thx @TheWinchesters for noticing)
 * updated README accordingly
2018-11-18 15:31:14 +01:00
Sarjuuk
0286cb20f1 Spell/TaxiPath
* resolve taxi path routes on spells
 * fixed display of taxi paths on maps
2018-10-07 12:55:38 +02:00
Sarjuuk
31ec17d279 Pages/Quest
* escape placeholder tags in quest name for display
2018-08-26 16:49:29 +02:00
Sarjuuk
6f578b31e0 Pages/Achivement
* resorted criteria to match ingame order
2018-08-26 16:41:14 +02:00
Sarjuuk
0fd0a66137 Lang/Races
Hey Lang, what races does this thing have assigned?
  Yes!

   .. this will no longer happen
2018-08-16 21:49:16 +02:00
Sarjuuk
ceedf3fbdd Search
* made racial traits searchable under abilities
2018-08-11 23:13:09 +02:00
Sarjuuk
ed614d3938 Objects/Filters
* added filter for common spell foci
2018-07-29 22:41:32 +02:00
Sarjuuk
d32f21f9ca Spells/DetailPage
* display spells affectged by category cooldowns

Items/DetailPage
 * display items affected by category cooldowns defined in spell template
2018-07-20 17:34:33 +02:00
Sarjuuk
e0150feda6 implemented type areatrigger
* staff only
2018-07-16 21:08:18 +02:00
Sarjuuk
56ccc592a6 Sounds/DetailPage
* fixed generating links to play via the WoW API
 * fixed usage of not yet available PlaySoundKitID() when selecting file from list
 * while internally it is pretty laissez-faire about cases in file paths, suddenly WoW DOES care, when it comes to its LUA API.
   so .. don't cast to lowercase when storing sounds in DB
2018-06-11 23:17:55 +02:00
Sarjuuk
04e183f5e3 Spells/Filters
* add various filters (mostly attribute flags)
 * resolve additional dbc-data on detail page
 * fixed DK rune indexing
2018-06-06 22:31:56 +02:00
Sarjuuk
adc1273b08 Ajax/Debug
* be a lot more verbose when errors are occuring (to staff anyway)
 * made hardcoded error messages for comments localizable
 * add error messages from posting comments to session to be displayed on next page update
2018-05-26 21:02:19 +02:00
Sarjuuk
93a72013b8 Reports
* added timestamp column to db table
 * consolidated write operation
 * fixed storing reports when get_browser() would return empty
2018-05-26 15:59:01 +02:00
Sarjuuk
c5d28a9bbc Profiler/Chars
* prefix characters flagged for rename to prevent collision with real char names (multiple chars flagged for rename will still collide, but what the hoo haa)
 * alter table index to prevent creation and subsequent conflict with duplicate entries of the same char (realm/guid pair)
2018-05-26 13:29:23 +02:00
Sarjuuk
7dc283f649 Misc/Fixups
* squash multiple notices from error log
 * fixed copy&paste error preventing view on custom profiles
 * fixed display of spell requirements if mount was required after changs to mount sorting
 * properly get/send error code in profile synchronisation
 * use clear:left after floating mail block in template
2018-05-26 12:18:02 +02:00
Sarjuuk
9645ad0877 Localization/deDE
* kitsch is inappropriate???
2018-05-13 14:41:23 +02:00
Sarjuuk
45f325a0fa Admin/WeightPresets
* removed redundant code
 * fixed empty scales not being written to dataset as expected
2018-05-13 12:41:54 +02:00
Sarjuuk
37652ce011 Profiler/Raids
* fixed displaying individual encounters in raid activity tracker
2018-05-13 12:24:00 +02:00
Sarjuuk
594b2269bd Profiler/Profiles
* fixed regular characters being hidden by default
2018-05-13 12:03:44 +02:00
Sarjuuk
55a554ca06 Spells/Tooltips
* remove level scaling from tooltips (for now)
2018-05-11 13:14:06 +02:00
Sarjuuk
a0198ae5f4 Spells/Mounts
* group mounts into ground, flying & misc.
 * display mount speed bonus in listview
2018-05-10 22:14:13 +02:00
Sarjuuk
3bbd7f97da Item/ExtendedCost
* display a "sells" entry for every combination of currencies the vendor accepts per item
 * display every cost combination in the quick infos on the item detail page
2018-05-10 20:04:24 +02:00
Sarjuuk
761da59ee9 Localization/Chinese
* added support for locale 4
* some strings are missing and will need to be translated at a later date
* thx @qyh214 for a lot of content

Co-authored-by: qyh214 <sandy0214qin@msn.com>
2018-05-08 20:36:52 +02:00
Sarjuuk
09d2013012 Misc/Fixups
* fixed typo inlocalization
 * have the profiler queue run at least one cycle so it can get registered as successfully started
 * a map popup will no longer scroll the page to bottom
 * replaced an input placeholder image with proper placeholder property
2018-05-07 22:51:18 +02:00
Sarjuuk
4f0d045ff4 Quests/Misc
* remove Uldaman from Eastern Kingdoms
 * add Crystalsong forest to Northrend
2018-05-05 17:42:24 +02:00
Sarjuuk
e2e23c430a Quests/Categories
* shuffled quests into a structure that seems sensible (menus should no longer point to dead pages or display 'undefined' links)
2018-05-01 19:09:38 +02:00
Sarjuuk
9609c93f8c Quest/Detail Page
* display mail attachments besides the actual mail instead of extra tab
2018-04-30 10:59:24 +02:00
Sarjuuk
35dc835c62 NPCs/Detail Page
* fixed vendors displaying the same sell price for every item
2018-04-28 13:32:16 +02:00
Sarjuuk
808bbcc2fd Profiler/Profiles
* do not display deleted custom profiles in their parent profiles quick info
2018-04-28 12:03:52 +02:00
Sarjuuk
c5e9762830 Profiler/Profiles
* allow strings with less then 3 chars in name search
 * store name as binary to allow accent-sensitive search
   (note: guilds and arenateams have the same issue but utf8mb4_bin can't be applied blindly as the search also becomes case-sensitive (wich is already handled for profiles))
2018-04-28 11:32:22 +02:00
Sarjuuk
b13f3af03e Spell/Detail Page
* display effective MiscValue for effect PowerBurn
2018-04-28 10:57:41 +02:00
Sarjuuk
000a3208c0 Items/Armor
* fixed handling of additional Armor
2018-04-28 10:55:25 +02:00
Sarjuuk
efd68bdb5f Profiler
* fixed possible bug where a custom profile would load instead of the original character it was created from
2018-04-22 12:02:53 +02:00
Sarjuuk
d0b7fa9ef5 Profiler
* reworked default exclusions (hid some things entirely, flagged others for appropriate cases)
 * char names consisting of two letters are now valid
 * fixed matching enchantments onto ranged weapons
2018-04-22 11:48:30 +02:00
Sarjuuk
9dcb0b3a15 Profiler/Mounts
* display class-specific mounts on their respective classes
2018-04-08 11:28:46 +02:00
Sarjuuk
2518e4730b BaseType
* revert change to get number total results
   this partially reverts bf42973c
2018-04-07 20:52:10 +02:00
Sarjuuk
74308da407 Setup/Spells
* hide a lot of internal spells from searches
 * apply a faction to mount listing, so the Profiler can hide mounts from the opposite faction
 * apply GS to profession enchantments
2018-04-07 20:34:01 +02:00
Sarjuuk
bb9e6a36dd pages/Search
* fix searching by stat weights (used in Profiler, Item Comparison)
2018-04-07 20:31:50 +02:00
Sarjuuk
c448207724 JS/Locales
* grep-fix multiple issues with replacement patterns and hrefs
2018-04-07 20:30:40 +02:00
Sarjuuk
81335ad6dc Profiler/Reputation
* add missing base values for each race/class
2018-04-02 15:18:28 +02:00
Sarjuuk
d46a78b3cb Misc/Typos
* fixed display of charges on GameObject Infobox
 * removed display of achievement points as cost from vendors
2018-04-01 12:12:43 +02:00
Sarjuuk
492b7cd3c4 Profiler/Tooltips
* fixed display of titles
 * supply iconstring in the tooltip data as this does not rely on variables within global.js
   fixes externally embeded profile tooltips
2018-03-31 23:59:27 +02:00
Sarjuuk
c19691033a Profiler/Raid-Tracker
* sort raids by difficulty to fix progress bars
 * rename raids to include raid sizes
2018-03-31 17:25:11 +02:00
Sarjuuk
164cdc234c Profiler
* fixed directly accessing existing, but not yet fetched, profile
2018-03-31 17:21:24 +02:00
Sarjuuk
394a2699d8 Profiler
* translate anouncements for loc 3
 * fix queue locking up on error
 * fix queue status display
 * fix flagging real characters as private
2018-03-31 17:21:24 +02:00
Sarjuuk
e50333a518 DB/Favorites
* implement favorites for DB entries
 * click on the star besides the name of the entry to add it to a new quick menu
2018-03-30 19:22:45 +02:00
Sarjuuk
d848d316fe Misc/Typo 2018-03-29 23:52:02 +02:00
Sarjuuk
fa46aefa27 Modelviewer
* removed quality options (java .. holy crap!)
 * added animations menu
2018-03-29 21:19:36 +02:00
Sarjuuk
0912248fd5 Misc/Typos 2018-03-29 18:59:09 +02:00
Sarjuuk
fab71f9325 Misc
* don't cache playlists
 * don't cache new custom profiles
 * forgot to sanitize and use param from js
2018-03-29 14:40:46 +02:00
Sarjuuk
51eda12099 Pages/Home
* implement random home titles
2018-03-29 13:16:23 +02:00
Sarjuuk
bf42973c00 Profiler
* further optimize search
   - use achievement 4496 as shortcut for everything based around total achievement points
   - get talent distribution separately
   - get total profiler-items found separately
   - opt to not sort found results
 * fixed Profiles with zero Achievement Points
 * added cache key genrator i forgot :<
 * fixed typos
2018-03-29 12:47:27 +02:00
Sarjuuk
431e984f03 DB/Structure
* update collate to use utf8mb4
 * fixed creature paths being capped at 255 waypoints
2018-03-28 11:59:13 +02:00
Sarjuuk
e973e5e33b Profiler/RaidTracker
* ALL the Raids .. track em
2018-03-28 11:55:06 +02:00
Sarjuuk
22d02378ef Config/Profiler
* update config to enable/disable profiler in general, instead of just the queue
 * regenerate affected files on config change
2018-03-27 12:41:32 +02:00
Sarjuuk
c17cf9c043 Profiler/Profile
* shorten raid activity string for locale deDE to prevent icons from showing up where they shouldn't (also needed for locale ruRU)
 * define offhand frill as unfit for enchantments
 * resort raid activity tracker and add placeholder for ulduar
2018-03-27 00:03:56 +02:00
Sarjuuk
72c1dacd3f Profiler/Guilds
* fixed forcing guildnames to string for js
 * made static string localized
2018-03-26 22:09:53 +02:00
Sarjuuk
bc834245d7 Setup/Sounds
* make log more clear, that every available locale is checked
2018-03-26 19:58:32 +02:00
333 changed files with 38961 additions and 13336 deletions

22
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,22 @@
---
name: Bug report
about: issue template
title: ''
labels: ''
assignees: ''
---
**Describe the bug and how to reproduce it**
additionally paste relevant lines from db table `aowow_errors`
or your browsers console here.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System:**
- OS: [e.g. Win10]
- PHP version:
- revision used:
- Browser (in case of JavaScript / display errors):
- AzerothCore: yes/no

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@
# generated files
/static/js/profile_all.js
/static/js/locale.js
/static/js/Markup.js
/static/widgets/power.js
/static/widgets/power/demo.html
/static/widgets/searchbox.js

View File

@@ -18,23 +18,26 @@ Also, this project is not meant to be used for commercial puposes of any kind!
## Requirements
+ Webserver running PHP ≥ 5.5.0 including extensions:
+ SimpleXML
+ GD
+ Mysqli
+ mbString
+ Webserver running PHP ≥ 7.4 — 8.0 including extensions:
+ [SimpleXML](https://www.php.net/manual/en/book.simplexml.php)
+ [GD](https://www.php.net/manual/en/book.image)
+ [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
+ [Multibyte String](https://www.php.net/manual/en/book.mbstring.php)
+ [File Information](https://www.php.net/manual/en/book.fileinfo.php)
+ [GNU Multiple Precision](https://www.php.net/manual/en/book.gmp.php) (When using TrinityCore as auth source)
+ MySQL ≥ 5.5.30
+ [TDB 335.63](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.63) - including world updates up to 04.05.2017
+ [TDB 335.21101](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.21101)
+ WIN: php.exe needs to be added to the `PATH` system variable, if it isn't already.
+ Tools require cmake: Please refer to the individual repositories for detailed information
+ [MPQExtractor](https://github.com/Sarjuuk/MPQExtractor) / [FFmpeg](https://ffmpeg.org/download.html) / [BLPConverter](https://github.com/Sarjuuk/BLPConverter) (optional)
+ [MPQExtractor](https://github.com/Sarjuuk/MPQExtractor) / [FFmpeg](https://ffmpeg.org/download.html) / (optional: [BLPConverter](https://github.com/Sarjuuk/BLPConverter))
+ WIN users may find it easier to use these alternatives
+ [MPQEditor](http://www.zezula.net/en/mpq/download.html) / [FFmpeg](http://ffmpeg.zeranoe.com/builds/) / [BLPConverter](https://github.com/PatrickCyr/BLPConverter) (optional)
+ [MPQEditor](http://www.zezula.net/en/mpq/download.html) / [FFmpeg](http://ffmpeg.zeranoe.com/builds/) / (optional: [BLPConverter](https://github.com/PatrickCyr/BLPConverter))
audio processing may require [lame](https://sourceforge.net/projects/lame/files/lame/3.99/) or [vorbis-tools](https://www.xiph.org/downloads/) (which may require libvorbis (which may require libogg))
#### Highly Recommended
+ setting the following configuration values on your TrintyCore server will greatly increase the accuracy of spawn points
+ setting the following configuration values on your TrinityCore server will greatly increase the accuracy of spawn points
> Calculate.Creature.Zone.Area.Data = 1
> Calculate.Gameoject.Zone.Area.Data = 1
@@ -89,7 +92,7 @@ WAV-files need to be reencoded as `ogg/vorbis` and some MP3s may identify themse
* [example for \*nix](https://gist.github.com/Sarjuuk/1f05ef2affe49a7e7ca0fad7b01c081d)
#### 6. Run the initial setup from the CLI
`php aowow --firstrun`.
`php aowow --setup`.
This should guide you through with minimal input required from your end, but will take some time though, especially compiling the zone-images. Use it to familiarize yourself with the other functions this setup has. Yes, I'm dead serious: *Go read the code!* It will help you understand how to configure AoWoW and keep it in sync with your world database.
When you've created your admin account you are done.
@@ -116,12 +119,18 @@ A: Some server configurations or external services (like Cloudflare) come with m
Q: Some search results within the profiler act rather strange. How does it work?
A: Whenever you try to view a new character, AoWoW needs to fetch it first. Since the data is structured for the needs of TrinityCore and not for easy viewing, AoWoW needs to save and restructure it locally. To this end, every char request is placed in a queue. While the queue is not empty, a single instance of `prQueue` is run in the background as not to overwhelm the characters database with requests. This also means, some more exotic search queries can't be run agains the characters database and have to use the incomplete/outdated cached profiles of AoWoW.
Q: Screenshot upload fails, because the file size is too large and/or the subdirectories are visible from the web!
A: That's a web server configuration issue. If you are using Apache you may need to [enable the use of .htaccess](http://httpd.apache.org/docs/2.4/de/mod/core.html#allowoverride). Other servers require individual configuration.
Q: An Item, Quest or NPC i added or edited can't be searched. Why?
A: A search is only conducted against the currently used locale. You may have only edited the name field in the base table instead of adding multiple strings into the appropriate \*_locale tables. In this case searches in a non-english locale are run against an empty name field.
## Thanks
@mix: for providing the php-script to parse .blp and .dbc into usable images and tables
@LordJZ: the wrapper-class for DBSimple; the basic idea for the user-class
@kliver: basic implementation of screenshot uploads
@Sarjuuk: maintainer of the project
## Special Thanks

View File

@@ -1,49 +0,0 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// use as example. File is generated from setup.
// -- Aowow Database --
// contains world-data, user-data and logs
$AoWoWconf['aowow'] = array(
'host' => '127.0.0.1',
'user' => '<user>',
'pass' => '<pass>',
'db' => 'world',
'prefix' => 'aowow_'
);
// -- World Database --
// used to generate data-tables
$AoWoWconf['world'] = array(
'host' => '127.0.0.1',
'user' => '<user>',
'pass' => '<pass>',
'db' => 'world',
'prefix' => ''
);
// -- Auth Database --
// used to generate user-tables
$AoWoWconf['auth'] = array(
'host' => '127.0.0.1',
'user' => '<user>',
'pass' => '<pass>',
'db' => 'auth',
'prefix' => ''
);
// -- Characters Database --
// used to display profiles
$AoWoWconf['characters'][<realmId>] = array(
'host' => '127.0.0.1',
'user' => '<user>',
'pass' => '<pass>',
'db' => 'characters',
'prefix' => ''
);
?>

View File

@@ -6,14 +6,13 @@ if (!defined('AOWOW_REVISION'))
class AjaxHandler
{
use TrRequestData;
protected $validParams = [];
protected $params = [];
protected $handler;
protected $contentType = 'application/x-javascript; charset=utf-8';
protected $_post = [];
protected $_get = [];
protected $contentType = MIME_TYPE_JSON;
public $doRedirect = false;
@@ -21,14 +20,10 @@ class AjaxHandler
{
$this->params = $params;
foreach ($this->_post as $k => &$v)
$v = isset($_POST[$k]) ? filter_input(INPUT_POST, $k, $v[0], $v[1]) : null;
foreach ($this->_get as $k => &$v)
$v = isset($_GET[$k]) ? filter_input(INPUT_GET, $k, $v[0], $v[1]) : null;
$this->initRequestData();
}
public function handle(&$out)
public function handle(string &$out) : bool
{
if (!$this->handler)
return false;
@@ -43,49 +38,34 @@ class AjaxHandler
}
$h = $this->handler;
$out = (string)$this->$h();
$out = $this->$h();
if ($out === null)
$out = '';
return true;
}
public function getContentType()
public function getContentType() : string
{
return $this->contentType;
}
protected function checkEmptySet($val)
protected function reqPOST(string ...$keys) : bool
{
return $val === ''; // parameter is expected to be empty
foreach ($keys as $k)
if (!isset($this->_post[$k]) || $this->_post[$k] === null || $this->_post[$k] === '')
return false;
return true;
}
protected function checkLocale($val)
protected function reqGET(string ...$keys) : bool
{
if (preg_match('/^'.implode('|', array_keys(array_filter(Util::$localeStrings))).'$/', $val))
return intval($val);
foreach ($keys as $k)
if (!isset($this->_get[$k]) || $this->_get[$k] === null || $this->_get[$k] === '')
return false;
return null;
}
protected function checkInt($val)
{
if (preg_match('/^-?\d+$/', $val))
return intval($val);
return null;
}
protected function checkIdList($val)
{
if (preg_match('/^-?\d+(,-?\d+)*$/', $val))
return array_map('intval', explode(',', $val));
return null;
}
protected function checkFulltext($val)
{
// trim non-printable chars
return preg_replace('/[\p{C}]/ui', '', $val);
return true;
}
}
?>

View File

@@ -5,20 +5,23 @@ if (!defined('AOWOW_REVISION'))
class AjaxAccount extends AjaxHandler
{
protected $validParams = ['exclude', 'weightscales'];
protected $validParams = ['exclude', 'weightscales', 'favorites'];
protected $_post = array(
'groups' => [FILTER_SANITIZE_NUMBER_INT, null],
'save' => [FILTER_SANITIZE_NUMBER_INT, null],
'delete' => [FILTER_SANITIZE_NUMBER_INT, null],
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']],
'name' => [FILTER_CALLBACK, ['options' => 'AjaxAccount::checkName']],
'scale' => [FILTER_CALLBACK, ['options' => 'AjaxAccount::checkScale']],
'reset' => [FILTER_SANITIZE_NUMBER_INT, null],
'mode' => [FILTER_SANITIZE_NUMBER_INT, null],
'type' => [FILTER_SANITIZE_NUMBER_INT, null],
'groups' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'save' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'delete' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList'],
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAccount::checkName' ],
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAccount::checkScale' ],
'reset' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'mode' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'type' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'add' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
// 'sessionKey' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
);
protected $_get = array(
'locale' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkLocale']]
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
);
public function __construct(array $params)
@@ -36,20 +39,22 @@ class AjaxAccount extends AjaxHandler
$this->handler = 'handleExclude';
else if ($this->params[0] == 'weightscales')
$this->handler = 'handleWeightscales';
else if ($this->params[0] == 'favorites')
$this->handler = 'handleFavorites';
}
protected function handleExclude()
protected function handleExclude() : void
{
if (!User::$id)
return;
if ($this->_post['mode'] == 1) // directly set exludes
{
$type = $this->_post['type'];
$ids = $this->_post['id'];
if (!isset(Util::$typeStrings[$type]) || empty($ids))
if (!Type::exists($type) || empty($ids))
{
trigger_error('AjaxAccount::handleExclude - invalid type #'.$type.(empty($ids) ? ' or id-list empty' : ''), E_USER_ERROR);
return;
}
// ready for some bullshit? here it comes!
// we don't get signaled whether an id should be added to or removed from either includes or excludes
@@ -73,23 +78,27 @@ class AjaxAccount extends AjaxHandler
$mask = $this->_post['groups'] & PR_EXCLUDE_GROUP_ANY;
DB::Aowow()->query('UPDATE ?_account SET excludeGroups = ?d WHERE id = ?d', $mask, User::$id);
return;
}
protected function handleWeightscales()
protected function handleWeightscales() : string
{
if ($this->_post['save'])
{
if (!$this->_post['scale'])
return 0;
{
trigger_error('AjaxAccount::handleWeightscales - scaleId empty', E_USER_ERROR);
return '0';
}
$id = 0;
if ($this->_post['id'] && ($id = $this->_post['id'][0]))
{
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account_weightscales WHERE userId = ?d AND id = ?d', User::$id, $id))
return 0;
{
trigger_error('AjaxAccount::handleWeightscales - scale #'.$id.' not in db or owned by user #'.User::$id, E_USER_ERROR);
return '0';
}
DB::Aowow()->query('UPDATE ?_account_weightscales SET `name` = ? WHERE id = ?d', $this->_post['name'], $id);
}
@@ -97,7 +106,7 @@ class AjaxAccount extends AjaxHandler
{
$nScales = DB::Aowow()->selectCell('SELECT COUNT(id) FROM ?_account_weightscales WHERE userId = ?d', User::$id);
if ($nScales >= 5) // more or less hard-defined in LANG.message_weightscalesaveerror
return 0;
return '0';
$id = DB::Aowow()->query('INSERT INTO ?_account_weightscales (`userId`, `name`) VALUES (?d, ?)', User::$id, $this->_post['name']);
}
@@ -106,33 +115,64 @@ class AjaxAccount extends AjaxHandler
foreach (explode(',', $this->_post['scale']) as $s)
{
list($k, $v) = explode(':', $s);
[$k, $v] = explode(':', $s);
if (!in_array($k, Util::$weightScales) || $v < 1)
continue;
DB::Aowow()->query('INSERT INTO ?_account_weightscale_data VALUES (?d, ?, ?d)', $id, $k, $v);
}
return $id;
return (string)$id;
}
else if ($this->_post['delete'] && $this->_post['id'] && $this->_post['id'][0])
DB::Aowow()->query('DELETE FROM ?_account_weightscales WHERE id = ?d AND userId = ?d', $this->_post['id'][0], User::$id);
else
return 0;
{
trigger_error('AjaxAccount::handleWeightscales - malformed request received', E_USER_ERROR);
return '0';
}
}
protected function checkScale($val)
protected function handleFavorites() : void
{
// omit usage of sessionKey
if (count($this->_post['id']) != 1 || empty($this->_post['id'][0]))
{
trigger_error('AjaxAccount::handleFavorites - malformed request received', E_USER_ERROR);
return;
}
$typeId = $this->_post['id'][0];
if ($type = $this->_post['add'])
{
$tc = Type::newList($type, [['id', $typeId]]);
if (!$tc || $tc->error)
{
trigger_error('AjaxAccount::handleFavorites - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR);
return;
}
DB::Aowow()->query('INSERT INTO ?_account_favorites (`userId`, `type`, `typeId`) VALUES (?d, ?d, ?d)', User::$id, $type, $typeId);
}
else if ($type = $this->_post['remove'])
DB::Aowow()->query('DELETE FROM ?_account_favorites WHERE `userId` = ?d AND `type` = ?d AND `typeId` = ?d', User::$id, $type, $typeId);
}
protected static function checkScale(string $val) : string
{
if (preg_match('/^((\w+:\d+)(,\w+:\d+)*)$/', $val))
return $val;
return null;
return '';
}
protected function checkName($val)
protected static function checkName(string $val) : string
{
$var = trim(urldecode($val));
return filter_var($var, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
return filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_AOWOW);
}
}
?>

View File

@@ -5,33 +5,37 @@ if (!defined('AOWOW_REVISION'))
class AjaxAdmin extends AjaxHandler
{
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets'];
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets', 'spawn-override', 'guide'];
protected $_get = array(
'action' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
'id' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkId']],
'key' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkKey']],
'all' => [FILTER_UNSAFE_RAW, null],
'type' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'typeid' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'user' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkUser']],
'val' => [FILTER_UNSAFE_RAW, null]
'action' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned'],
'key' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkKey' ],
'all' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkUser' ],
'val' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
'guid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
'area' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
'floor' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ]
);
protected $_post = array(
'alt' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'id' => [FILTER_SANITIZE_NUMBER_INT, null],
'scale' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkScale']],
'__icon' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkKey']],
'alt' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkScale'],
'__icon' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkKey' ],
'status' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
'msg' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW]
);
public function __construct(array $params)
{
parent::__construct($params);
// requires 'action' parameter in any case
if (!$this->_get['action'] || !$this->params)
if (!$this->params)
return;
if ($this->params[0] == 'screenshots')
if ($this->params[0] == 'screenshots' && $this->_get['action'])
{
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT))
return;
@@ -51,7 +55,7 @@ class AjaxAdmin extends AjaxHandler
else if ($this->_get['action'] == 'relocate')
$this->handler = 'ssRelocate';
}
else if ($this->params[0] == 'siteconfig')
else if ($this->params[0] == 'siteconfig' && $this->_get['action'])
{
if (!User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
return;
@@ -63,7 +67,7 @@ class AjaxAdmin extends AjaxHandler
else if ($this->_get['action'] == 'update')
$this->handler = 'confUpdate';
}
else if ($this->params[0] == 'weight-presets')
else if ($this->params[0] == 'weight-presets' && $this->_get['action'])
{
if (!User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_BUREAU))
return;
@@ -71,11 +75,25 @@ class AjaxAdmin extends AjaxHandler
if ($this->_get['action'] == 'save')
$this->handler = 'wtSave';
}
else if ($this->params[0] == 'spawn-override')
{
if (!User::isInGroup(U_GROUP_MODERATOR))
return;
$this->handler = 'spawnPosFix';
}
else if ($this->params[0] == 'guide')
{
if (!User::isInGroup(U_GROUP_STAFF))
return;
$this->handler = 'guideManage';
}
}
// get all => null (optional)
// evaled response .. UNK
protected function ssList()
protected function ssList() : string
{
// ssm_screenshotPages
// ssm_numPagesFound
@@ -89,7 +107,7 @@ class AjaxAdmin extends AjaxHandler
// get: [type => type, typeId => typeId] || [user => username]
// evaled response .. UNK
protected function ssManage()
protected function ssManage() : string
{
$res = [];
@@ -104,21 +122,22 @@ class AjaxAdmin extends AjaxHandler
// get: id => SSid
// resp: ''
protected function ssEditAlt()
protected function ssEditAlt() : void
{
// doesn't need to be htmlEscaped, ths javascript does that
if ($this->_get['id'] && $this->_post['alt'] !== null)
DB::Aowow()->query('UPDATE ?_screenshots SET caption = ? WHERE id = ?d', trim($this->_post['alt']), $this->_get['id'][0]);
return '';
}
// get: id => comma-separated SSids
// resp: ''
protected function ssApprove()
protected function ssApprove() : void
{
if (!$this->_get['id'])
return '';
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssApprove - screenshotId empty', E_USER_ERROR);
return;
}
// create resized and thumb version of screenshot
$resized = [772, 618];
@@ -128,11 +147,14 @@ class AjaxAdmin extends AjaxHandler
foreach ($this->_get['id'] as $id)
{
// must not be already approved
if ($_ = DB::Aowow()->selectRow('SELECT userIdOwner, date, type, typeId FROM ?_screenshots WHERE (status & ?d) = 0 AND id = ?d', CC_FLAG_APPROVED, $id))
if ($ssEntry = DB::Aowow()->selectRow('SELECT userIdOwner, date, type, typeId FROM ?_screenshots WHERE (status & ?d) = 0 AND id = ?d', CC_FLAG_APPROVED, $id))
{
// should also error-log
if (!file_exists(sprintf($path, 'pending', $id)))
{
trigger_error('AjaxAdmin::ssApprove - screenshot #'.$id.' exists in db but not as file', E_USER_ERROR);
continue;
}
$srcImg = imagecreatefromjpeg(sprintf($path, 'pending', $id));
$srcW = imagesx($srcImg);
@@ -170,22 +192,27 @@ class AjaxAdmin extends AjaxHandler
// set as approved in DB and gain rep (once!)
DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, userIdApprove = ?d WHERE id = ?d', CC_FLAG_APPROVED, User::$id, $id);
Util::gainSiteReputation($_['userIdOwner'], SITEREP_ACTION_UPLOAD, ['id' => $id, 'what' => 1, 'date' => $_['date']]);
Util::gainSiteReputation($ssEntry['userIdOwner'], SITEREP_ACTION_UPLOAD, ['id' => $id, 'what' => 1, 'date' => $ssEntry['date']]);
// flag DB entry as having screenshots
if (Util::$typeClasses[$_['type']] && ($tbl = get_class_vars(Util::$typeClasses[$_['type']])['dataTable']))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $_['typeId']);
if ($tbl = Type::getClassAttrib($ssEntry['type'], 'dataTable'))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $ssEntry['typeId']);
}
else
trigger_error('AjaxAdmin::ssApprove - screenshot #'.$id.' not in db or already approved', E_USER_ERROR);
}
return '';
return;
}
// get: id => comma-separated SSids
// resp: ''
protected function ssSticky()
protected function ssSticky() : void
{
if (!$this->_get['id'])
return '';
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssSticky - screenshotId empty', E_USER_ERROR);
return;
}
// approve soon to be sticky screenshots
$this->ssApprove();
@@ -201,17 +228,18 @@ class AjaxAdmin extends AjaxHandler
// toggle sticky status
DB::Aowow()->query('UPDATE ?_screenshots SET `status` = IF(`status` & ?d, `status` & ~?d, `status` | ?d) WHERE id = ?d AND `status` & ?d', CC_FLAG_STICKY, CC_FLAG_STICKY, CC_FLAG_STICKY, $id, CC_FLAG_APPROVED);
}
return '';
}
// get: id => comma-separated SSids
// resp: ''
// 2 steps: 1) remove from sight, 2) remove from disk
protected function ssDelete()
protected function ssDelete() : void
{
if (!$this->_get['id'])
return '';
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssDelete - screenshotId empty', E_USER_ERROR);
return;
}
$path = 'static/uploads/screenshots/%s/%d.jpg';
@@ -247,26 +275,27 @@ class AjaxAdmin extends AjaxHandler
{
$typeIds = explode(',', $typeIds);
$toUnflag = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(BIT_OR(`status`) & ?d, 1, 0) AS hasMore FROM ?_screenshots WHERE `type` = ?d AND typeId IN (?a) GROUP BY typeId HAVING hasMore = 0', CC_FLAG_APPROVED, $type, $typeIds);
if ($toUnflag && Util::$typeClasses[$type] && ($tbl = get_class_vars(Util::$typeClasses[$type])['dataTable']))
if ($toUnflag && ($tbl = Type::getClassAttrib($type, 'dataTable')))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags & ~?d WHERE id IN (?a)', CUSTOM_HAS_SCREENSHOT, array_keys($toUnflag));
}
return '';
}
// get: id => ssId, typeid => typeId (but not type..?)
// resp: ''
protected function ssRelocate()
protected function ssRelocate() : void
{
if (!$this->_get['id'] || !$this->_get['typeid'])
return '';
if (!$this->reqGET('id', 'typeid'))
{
trigger_error('AjaxAdmin::ssRelocate - screenshotId or typeId empty', E_USER_ERROR);
return;
}
$id = $this->_get['id'][0];
list($type, $oldTypeId) = array_values(DB::Aowow()->selectRow('SELECT type, typeId FROM ?_screenshots WHERE id = ?d', $id));
[$type, $oldTypeId] = array_values(DB::Aowow()->selectRow('SELECT type, typeId FROM ?_screenshots WHERE id = ?d', $id));
$typeId = (int)$this->_get['typeid'];
$tc = new Util::$typeClasses[$type]([['id', $typeId]]);
if (!$tc->error)
$tc = Type::newList($type, [['id', $typeId]]);
if ($tc && !$tc->error)
{
// move screenshot
DB::Aowow()->query('UPDATE ?_screenshots SET typeId = ?d WHERE id = ?d', $typeId, $id);
@@ -279,11 +308,11 @@ class AjaxAdmin extends AjaxHandler
if($ssInfo || !$ssInfo['hasMore'])
DB::Aowow()->query('UPDATE '.$tc::$dataTable.' SET cuFlags = cuFlags & ~?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $oldTypeId);
}
return '';
else
trigger_error('AjaxAdmin::ssRelocate - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR);
}
protected function confAdd()
protected function confAdd() : string
{
$key = trim($this->_get['key']);
$val = trim(urldecode($this->_get['val']));
@@ -304,9 +333,9 @@ class AjaxAdmin extends AjaxHandler
return '';
}
protected function confRemove()
protected function confRemove() : string
{
if (!$this->_get['key'])
if (!$this->reqGET('key'))
return 'invalid configuration option given';
if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->_get['key'], CON_FLAG_PERSISTENT))
@@ -315,7 +344,7 @@ class AjaxAdmin extends AjaxHandler
return 'option name is either protected or was not found';
}
protected function confUpdate()
protected function confUpdate() : string
{
$key = trim($this->_get['key']);
$val = trim(urldecode($this->_get['val']));
@@ -334,8 +363,8 @@ class AjaxAdmin extends AjaxHandler
return "value must be integer";
else if ($cfg['flags'] & CON_FLAG_TYPE_FLOAT && !preg_match('/^-?\d*(,|.)?\d+$/i', $val))
return "value must be float";
else if ($cfg['flags'] & CON_FLAG_TYPE_BOOL)
$val = (int)!!$val; // *snort* bwahahaa
else if ($cfg['flags'] & CON_FLAG_TYPE_BOOL && $val != '1')
$val = '0';
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $val, $key);
if (!$this->confOnChange($key, $val, $msg))
@@ -344,84 +373,165 @@ class AjaxAdmin extends AjaxHandler
return $msg;
}
protected function wtSave()
protected function wtSave() : string
{
if (!$this->_post['id'] || !$this->_post['__icon'])
return 3;
$writeFile = function($file, $content)
{
$success = false;
if ($handle = @fOpen($file, "w"))
{
if (fWrite($handle, $content))
$success = true;
fClose($handle);
}
else
die('me no file');
if ($success)
@chmod($file, Util::FILE_ACCESS);
return $success;
};
if (!$this->reqPOST('id', '__icon'))
return '3';
// save to db
DB::Aowow()->query('DELETE FROM ?_account_weightscale_data WHERE id = ?d', $this->_post['id']);
DB::Aowow()->query('UPDATE ?_account_weightscales SET `icon`= ? WHERE `id` = ?d', $this->_post['__icon'], $this->_post['id']);
foreach (explode(',', $this->_post['scale']) as $s)
{
list($k, $v) = explode(':', $s);
[$k, $v] = explode(':', $s);
if (!in_array($k, Util::$weightScales) || $v < 1)
continue;
if (DB::Aowow()->query('INSERT INTO ?_account_weightscale_data VALUES (?d, ?, ?d)', $this->_post['id'], $k, $v) === null)
return 1;
return '1';
}
// write dataset
$wtPresets = [];
$scales = DB::Aowow()->select('SELECT id, name, icon, class FROM ?_account_weightscales WHERE userId = 0 ORDER BY class, id ASC');
foreach ($scales as $s)
{
$weights = DB::Aowow()->selectCol('SELECT field AS ARRAY_KEY, val FROM ?_account_weightscale_data WHERE id = ?d', $s['id']);
if (!$weights)
continue;
$wtPresets[$s['class']]['pve'][$s['name']] = array_merge(['__icon' => $s['icon']], $weights);
}
$toFile = "var wt_presets = ".Util::toJSON($wtPresets).";";
$file = 'datasets/weight-presets';
if (!$writeFile($file, $toFile))
return 2;
exec('php aowow --build=weightPresets', $out);
foreach ($out as $o)
if (strstr($o, 'ERR'))
return '2';
// all done
return 0;
return '0';
}
protected function checkId($val)
protected function spawnPosFix() : string
{
// expecting id-list
if (preg_match('/\d+(,\d+)*/', $val))
return array_map('intVal', explode(',', $val));
if (!$this->reqGET('type', 'guid', 'area', 'floor'))
return '-4';
return null;
$guid = $this->_get['guid'];
$type = $this->_get['type'];
$area = $this->_get['area'];
$floor = $this->_get['floor'];
if (!in_array($type, [Type::NPC, Type::OBJECT, Type::SOUND, Type::AREATRIGGER]))
return '-3';
DB::Aowow()->query('REPLACE INTO ?_spawns_override VALUES (?d, ?d, ?d, ?d, ?d)', $type, $guid, $area, $floor, AOWOW_REVISION);
if ($wPos = Game::getWorldPosForGUID($type, $guid))
{
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor))
{
$updGUIDs = [$guid];
$newPos = array(
'posX' => $point[0]['posX'],
'posY' => $point[0]['posY'],
'areaId' => $point[0]['areaId'],
'floor' => $point[0]['floor']
);
// if creature try for waypoints
if ($type == Type::NPC)
{
$jobs = array(
'SELECT -w.id AS `entry`, w.point AS `pointId`, w.position_y AS `posX`, w.position_x AS `posY` FROM creature_addon ca JOIN waypoint_data w ON w.id = ca.path_id WHERE ca.guid = ?d AND ca.path_id <> 0',
'SELECT `entry`, `pointId`, `location_y` AS `posX`, `location_x` AS `posY` FROM `script_waypoint` WHERE `entry` = ?d',
'SELECT `entry`, `pointId`, `position_y` AS `posX`, `position_x` AS `posY` FROM `waypoints` WHERE `entry` = ?d'
);
foreach ($jobs as $idx => $job)
{
if ($swp = DB::World()->select($job, $idx ? $wPos[$guid]['id'] : $guid))
{
foreach ($swp as $w)
{
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor))
{
$p = array(
'posX' => $point[0]['posX'],
'posY' => $point[0]['posY'],
'areaId' => $point[0]['areaId'],
'floor' => $point[0]['floor']
);
}
DB::Aowow()->query('UPDATE ?_creature_waypoints SET ?a WHERE `creatureOrPath` = ?d AND `point` = ?d', $p, $w['entry'], $w['pointId']);
}
}
}
protected function checkKey($val)
// also move linked vehicle accessories (on the very same position)
$updGUIDs = array_merge($updGUIDs, DB::Aowow()->selectCol('SELECT s2.guid FROM ?_spawns s1 JOIN ?_spawns s2 ON s1.posX = s2.posX AND s1.posY = s2.posY AND
s1.areaId = s2.areaId AND s1.floor = s2.floor AND s2.guid < 0 WHERE s1.guid = ?d', $guid));
}
DB::Aowow()->query('UPDATE ?_spawns SET ?a WHERE `type` = ?d AND `guid` IN (?a)', $newPos, $type, $updGUIDs);
return '1';
}
return '-2';
}
return '-1';
}
protected function guideManage() : string
{
$update = function (int $id, int $status, ?string $msg = null) : bool
{
if (!DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d WHERE `id` = ?d', $status, $id))
return false;
// set display rev to latest
if ($status == GUIDE_STATUS_APPROVED)
DB::Aowow()->query('UPDATE ?_guides SET `rev` = (SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1) WHERE `id` = ?d', Type::GUIDE, $id, $id);
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $id, time(), User::$id, $status);
if ($msg)
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)' , $id, time(), User::$id, $msg);
return true;
};
if (!$this->_post['id'])
trigger_error('AjaxHander::guideManage - malformed request: id: '.$this->_post['id'].', status: '.$this->_post['status']);
else
{
$guide = DB::Aowow()->selectRow('SELECT `userId`, `status` FROM ?_guides WHERE `id` = ?d', $this->_post['id']);
if (!$guide)
trigger_error('AjaxHander::guideManage - guide #'.$this->_post['id'].' not found');
else
{
if ($this->_post['status'] == $guide['status'])
trigger_error('AjaxHander::guideManage - guide #'.$this->_post['id'].' already has status #'.$this->_post['status']);
else
{
if ($this->_post['status'] == GUIDE_STATUS_APPROVED)
{
if ($update($this->_post['id'], GUIDE_STATUS_APPROVED, $this->_post['msg']))
{
Util::gainSiteReputation($guide['userId'], SITEREP_ACTION_ARTICLE, ['id' => $this->_post['id']]);
return '1';
}
else
return '-2';
}
else if ($this->_post['status'] == GUIDE_STATUS_REJECTED)
return $update($this->_post['id'], GUIDE_STATUS_REJECTED, $this->_post['msg']) ? '1' : '-2';
else
trigger_error('AjaxHander::guideManage - unhandled status change request');
}
}
}
return '-1';
}
/***************************/
/* additional input filter */
/***************************/
protected static function checkKey(string $val) : string
{
// expecting string
if (preg_match('/[^a-z0-9_\.\-]/i', $val))
@@ -430,25 +540,30 @@ class AjaxAdmin extends AjaxHandler
return strtolower($val);
}
protected function checkUser($val)
protected static function checkUser($val) : string
{
$n = Util::lower(trim(urldecode($val)));
if (User::isValidName($n))
return $n;
return null;
return '';
}
protected function checkScale($val)
protected static function checkScale($val) : string
{
if (preg_match('/^((\w+:\d+)(,\w+:\d+)*)$/', $val))
return $val;
return null;
return '';
}
private function confOnChange($key, $val, &$msg)
/**********/
/* helper */
/**********/
private static function confOnChange(string $key, string $val, string &$msg) : bool
{
$fn = $buildList = null;
@@ -466,12 +581,16 @@ class AjaxAdmin extends AjaxHandler
case 'static_host':
$buildList = 'searchplugin,power,searchboxBody,searchboxScript';
break;
case 'contact_email':
$buildList = 'markup';
break;
case 'locales':
$buildList = 'locales';
$msg .= ' * remember to rebuild all static files for the language you just added.<br />';
$msg .= ' * you can speed this up by supplying the regionCode to the setup: <pre class="q1">--locales=<regionCodes,> -f</pre>';
break;
case 'profiler_queue':
case 'profiler_enable':
$buildList = 'realms,realmMenu';
$fn = function($x) use (&$msg) {
if (!$x)
return true;
@@ -479,6 +598,17 @@ class AjaxAdmin extends AjaxHandler
return Profiler::queueStart($msg);
};
break;
case 'acc_auth_mode':
$fn = function($x) use (&$msg) {
if ($x == 1 && !extension_loaded('gmp'))
{
$msg .= 'PHP extension GMP is required to use TrinityCore as auth source, but is not currently enabled.<br />';
return false;
}
return true;
};
break;
default: // nothing to do, everything is fine
return true;
}
@@ -495,3 +625,5 @@ class AjaxAdmin extends AjaxHandler
return $fn ? $fn($val) : true;
}
}
?>

View File

@@ -7,8 +7,8 @@ class AjaxArenaTeam extends AjaxHandler
{
protected $validParams = ['resync', 'status'];
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']],
'profile' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
);
public function __construct(array $params)
@@ -35,16 +35,16 @@ class AjaxArenaTeam extends AjaxHandler
profile: <empty> [optional, also get related chars]
return: 1
*/
protected function handleResync()
protected function handleResync() : string
{
if ($teams = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_arena_team WHERE id IN (?a)', $this->_get['id']))
foreach ($teams as $t)
Profiler::scheduleResync(TYPE_ARENA_TEAM, $t['realm'], $t['realmGUID']);
Profiler::scheduleResync(Type::ARENA_TEAM, $t['realm'], $t['realmGUID']);
if ($this->_get['profile'])
if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles p JOIN ?_profiler_arena_team_member atm ON atm.profileId = p.id WHERE atm.arenaTeamId IN (?a)', $this->_get['id']))
foreach ($chars as $c)
Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']);
Profiler::scheduleResync(Type::PROFILE, $c['realm'], $c['realmGUID']);
return '1';
}
@@ -72,9 +72,9 @@ class AjaxArenaTeam extends AjaxHandler
1: char does not exist
2: armory gone
*/
protected function handleStatus()
protected function handleStatus() : string
{
$response = Profiler::resyncStatus(TYPE_ARENA_TEAM, $this->_get['id']);
$response = Profiler::resyncStatus(Type::ARENA_TEAM, $this->_get['id']);
return Util::toJSON($response);
}
}

View File

@@ -11,23 +11,23 @@ class AjaxComment extends AjaxHandler
const REPLY_LENGTH_MAX = 600;
protected $_post = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxComment::checkId']],
'body' => [FILTER_UNSAFE_RAW, null],// escaped by json_encode
'commentbody' => [FILTER_UNSAFE_RAW, null],// escaped by json_encode
'response' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'reason' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'remove' => [FILTER_SANITIZE_NUMBER_INT, null],
'commentId' => [FILTER_SANITIZE_NUMBER_INT, null],
'replyId' => [FILTER_SANITIZE_NUMBER_INT, null],
'sticky' => [FILTER_SANITIZE_NUMBER_INT, null],
// 'username' => [FILTER_SANITIZE_STRING, 0xC] // FILTER_FLAG_STRIP_LOW | *_HIGH
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned'],
'body' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
'commentbody' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
'response' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
'reason' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'commentId' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'replyId' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'sticky' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
// 'username' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
);
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'type' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'typeid' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'rating' => [FILTER_SANITIZE_NUMBER_INT, null]
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
'rating' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
);
public function __construct(array $params)
@@ -75,49 +75,82 @@ class AjaxComment extends AjaxHandler
}
// i .. have problems believing, that everything uses nifty ajax while adding comments requires a brutal header(Loacation: <wherever>), yet, thats how it is
protected function handleCommentAdd()
protected function handleCommentAdd() : string
{
if (!$this->_get['typeid'] || !$this->_get['type'] || !isset(Util::$typeClasses[$this->_get['type']]))
return; // whatever, we cant even send him back
if (!$this->_get['typeid'] || !$this->_get['type'] || !Type::exists($this->_get['type']))
{
trigger_error('AjaxComment::handleCommentAdd - malforemd request received', E_USER_ERROR);
return ''; // whatever, we cant even send him back
}
// this type cannot be commented on
if (!(get_class_vars(Util::$typeClasses[$this->_get['type']])['contribute'] & CONTRIBUTE_CO))
return;
if (!Type::checkClassAttrib($this->_get['type'], 'contribute', CONTRIBUTE_CO))
{
trigger_error('AjaxComment::handleCommentAdd - tried to comment on unsupported type #'.$this->_get['type'], E_USER_ERROR);
return '';
}
// trim to max length
if (!User::isInGroup(U_GROUP_MODERATOR) && mb_strlen($this->_post['commentbody']) > (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)))
$this->post['commentbody'] = mb_substr($this->_post['commentbody'], 0, (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)));
$this->_post['commentbody'] = mb_substr($this->_post['commentbody'], 0, (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)));
if (User::canComment() && !empty($this->_post['commentbody']) && mb_strlen($this->_post['commentbody']) >= self::COMMENT_LENGTH_MIN)
if (User::canComment())
{
if (!empty($this->_post['commentbody']) && mb_strlen($this->_post['commentbody']) >= self::COMMENT_LENGTH_MIN)
{
if ($postIdx = DB::Aowow()->query('INSERT INTO ?_comments (type, typeId, userId, roles, body, date) VALUES (?d, ?d, ?d, ?d, ?, UNIX_TIMESTAMP())', $this->_get['type'], $this->_get['typeid'], User::$id, User::$groups, $this->_post['commentbody']))
{
Util::gainSiteReputation(User::$id, SITEREP_ACTION_COMMENT, ['id' => $postIdx]);
// every comment starts with a rating of +1 and i guess the simplest thing to do is create a db-entry with the system as owner
DB::Aowow()->query('INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, 0, 1)', $postIdx);
DB::Aowow()->query('INSERT INTO ?_user_ratings (`type`, `entry`, `userId`, `value`) VALUES (?d, ?d, 0, 1)', RATING_COMMENT, $postIdx);
// flag target with hasComment
if ($tbl = get_class_vars(Util::$typeClasses[$this->_get['type']])['dataTable'])
if ($tbl = Type::getClassAttrib($this->_get['type'], 'dataTable'))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $this->_get['typeid']);
}
else
{
$_SESSION['error']['co'] = Lang::main('intError');
trigger_error('AjaxComment::handleCommentAdd - write to db failed', E_USER_ERROR);
}
}
else
$_SESSION['error']['co'] = Lang::main('textLength', [mb_strlen($this->_post['commentbody']), self::COMMENT_LENGTH_MIN, self::COMMENT_LENGTH_MAX]);
}
else
$_SESSION['error']['co'] = Lang::main('cannotComment');
$this->doRedirect = true;
return '?'.Util::$typeStrings[$this->_get['type']].'='.$this->_get['typeid'].'#comments';
$idOrUrl = $this->_get['typeid'];
if ($this->_get['type'] == Type::GUIDE)
if ($_ = DB::Aowow()->selectCell('SELECT `url` FROM ?_guides WHERE `id` = ?d', $this->_get['typeid']))
$idOrUrl = $_;
return '?'.Type::getFileString($this->_get['type']).'='.$idOrUrl.'#comments';
}
protected function handleCommentEdit()
protected function handleCommentEdit() : void
{
if ((!User::canComment() && !User::isInGroup(U_GROUP_MODERATOR)) || !$this->_get['id'] || !$this->_post['body'])
if (!User::canComment() && !User::isInGroup(U_GROUP_MODERATOR))
{
trigger_error('AjaxComment::handleCommentEdit - user #'.User::$id.' not allowed to edit', E_USER_ERROR);
return;
}
if (!$this->_get['id'] || !$this->_post['body'])
{
trigger_error('AjaxComment::handleCommentEdit - malforemd request received', E_USER_ERROR);
return;
}
if (mb_strlen($this->_post['body']) < self::COMMENT_LENGTH_MIN)
return;
return; // no point in reporting this trifle
// trim to max length
if (!User::isInGroup(U_GROUP_MODERATOR) && mb_strlen($this->_post['body']) > (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)))
$this->post['body'] = mb_substr($this->_post['body'], 0, (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)));
$this->_post['body'] = mb_substr($this->_post['body'], 0, (self::COMMENT_LENGTH_MAX * (User::isPremium() ? 3 : 1)));
$update = array(
'body' => $this->_post['body'],
@@ -135,10 +168,13 @@ class AjaxComment extends AjaxHandler
DB::Aowow()->query('UPDATE ?_comments SET editCount = editCount + 1, ?a WHERE id = ?d', $update, $this->_get['id']);
}
protected function handleCommentDelete()
protected function handleCommentDelete() : void
{
if (!$this->_post['id'] || !User::$id)
{
trigger_error('AjaxComment::handleCommentDelete - commentId empty or user not logged in', E_USER_ERROR);
return;
}
// in theory, there is a username passed alongside... lets just use the current user (see user.js)
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d, deleteUserId = ?d, deleteDate = UNIX_TIMESTAMP() WHERE id IN (?a){ AND userId = ?d}',
@@ -156,15 +192,20 @@ class AjaxComment extends AjaxHandler
$this->_post['id']
);
if (!$coInfo['hasMore'] && Util::$typeClasses[$coInfo['type']] && ($tbl = get_class_vars(Util::$typeClasses[$coInfo['type']])['dataTable']))
if (!$coInfo['hasMore'] && ($tbl = Type::getClassAttrib($coInfo['type'], 'dataTable')))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags & ~?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']);
}
else
trigger_error('AjaxComment::handleCommentDelete - user #'.User::$id.' could not flag comment #'.$this->_post['id'].' as deleted', E_USER_ERROR);
}
protected function handleCommentUndelete()
protected function handleCommentUndelete() : void
{
if (!$this->_post['id'] || !User::$id)
{
trigger_error('AjaxComment::handleCommentUndelete - commentId empty or user not logged in', E_USER_ERROR);
return;
}
// in theory, there is a username passed alongside... lets just use the current user (see user.js)
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id IN (?a){ AND userId = deleteUserId AND deleteUserId = ?d}',
@@ -177,28 +218,30 @@ class AjaxComment extends AjaxHandler
if ($ok)
{
$coInfo = DB::Aowow()->selectRow('SELECT type, typeId FROM ?_comments WHERE id = ?d', $this->_post['id']);
if (Util::$typeClasses[$coInfo['type']] && ($tbl = get_class_vars(Util::$typeClasses[$coInfo['type']])['dataTable']))
if ($tbl = Type::getClassAttrib($coInfo['type'], 'dataTable'))
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']);
}
else
trigger_error('AjaxComment::handleCommentUndelete - user #'.User::$id.' could not unflag comment #'.$this->_post['id'].' as deleted', E_USER_ERROR);
}
protected function handleCommentRating()
protected function handleCommentRating() : string
{
if (!$this->_get['id'])
return Util::toJSON(['success' => 0]);
if ($votes = DB::Aowow()->selectRow('SELECT 1 AS success, SUM(IF(value > 0, value, 0)) AS up, SUM(IF(value < 0, -value, 0)) AS down FROM ?_comments_rates WHERE commentId = ?d and userId <> 0 GROUP BY commentId', $this->_get['id']))
if ($votes = DB::Aowow()->selectRow('SELECT 1 AS success, SUM(IF(`value` > 0, `value`, 0)) AS up, SUM(IF(`value` < 0, -`value`, 0)) AS down FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d AND userId <> 0 GROUP BY `entry`', RATING_COMMENT, $this->_get['id']))
return Util::toJSON($votes);
else
return Util::toJSON(['success' => 1, 'up' => 0, 'down' => 0]);
}
protected function handleCommentVote()
protected function handleCommentVote() : string
{
if (!User::$id || !$this->_get['id'] || !$this->_get['rating'])
return Util::toJSON(['error' => 1, 'message' => Lang::main('genericError')]);
$target = DB::Aowow()->selectRow('SELECT c.userId AS owner, cr.value FROM ?_comments c LEFT JOIN ?_comments_rates cr ON cr.commentId = c.id AND cr.userId = ?d WHERE c.id = ?d', User::$id, $this->_get['id']);
$target = DB::Aowow()->selectRow('SELECT c.`userId` AS owner, ur.`value` FROM ?_comments c LEFT JOIN ?_user_ratings ur ON ur.`type` = ?d AND ur.`entry` = c.id AND ur.`userId` = ?d WHERE c.id = ?d', RATING_COMMENT, User::$id, $this->_get['id']);
$val = User::canSupervote() ? 2 : 1;
if ($this->_get['rating'] < 0)
$val *= -1;
@@ -213,9 +256,9 @@ class AjaxComment extends AjaxHandler
$ok = false;
// old and new have same sign; undo vote (user may have gained/lost access to superVote in the meantime)
if ($target['value'] && ($target['value'] < 0) == ($val < 0))
$ok = DB::Aowow()->query('DELETE FROM ?_comments_rates WHERE commentId = ?d AND userId = ?d', $this->_get['id'], User::$id);
$ok = DB::Aowow()->query('DELETE FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d AND `userId` = ?d', RATING_COMMENT, $this->_get['id'], User::$id);
else // replace, because we may be overwriting an old, opposing vote
if ($ok = DB::Aowow()->query('REPLACE INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)', (int)$this->_get['id'], User::$id, $val))
if ($ok = DB::Aowow()->query('REPLACE INTO ?_user_ratings (`type`, `entry`, `userId`, `value`) VALUES (?d, ?d, ?d, ?d)', RATING_COMMENT, (int)$this->_get['id'], User::$id, $val))
User::decrementDailyVotes(); // do not refund retracted votes!
if (!$ok)
@@ -229,10 +272,13 @@ class AjaxComment extends AjaxHandler
return Util::toJSON(['error' => 0]);
}
protected function handleCommentSticky()
protected function handleCommentSticky() : void
{
if (!$this->_post['id'] || !User::isInGroup(U_GROUP_MODERATOR))
{
trigger_error('AjaxComment::handleCommentSticky - commentId empty or user #'.User::$id.' not moderator', E_USER_ERROR);
return;
}
if ($this->_post['sticky'])
DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d WHERE id = ?d', CC_FLAG_STICKY, $this->_post['id'][0]);
@@ -240,12 +286,15 @@ class AjaxComment extends AjaxHandler
DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id = ?d', CC_FLAG_STICKY, $this->_post['id'][0]);
}
protected function handleCommentOutOfDate()
protected function handleCommentOutOfDate() : string
{
$this->contentType = 'text/plain';
$this->contentType = MIME_TYPE_TEXT;
if (!$this->_post['id'])
return 'The comment does not exist.';
{
trigger_error('AjaxComment::handleCommentOutOfDate - commentId empty', E_USER_ERROR);
return Lang::main('intError');
}
$ok = false;
if (User::isInGroup(U_GROUP_MODERATOR)) // directly mark as outdated
@@ -255,114 +304,127 @@ class AjaxComment extends AjaxHandler
else
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~0x4 WHERE id = ?d', $this->_post['id'][0]);
}
else if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND `userId` = ?d', 1, 17, $this->_post['id'][0], User::$id))
return Lang::main('alreadyReport');
else if (User::$id && !$this->_post['reason'] || mb_strlen($this->_post['reason']) < self::REPLY_LENGTH_MIN)
return 'Your message is too short.';
return Lang::main('textTooShort');
else if (User::$id) // only report as outdated
{
$ok = DB::Aowow()->query(
'INSERT INTO ?_reports (userId, mode, reason, subject, ip, description, userAgent, appName) VALUES (?d, 1, 17, ?d, ?, "<automated comment report>", ?, ?)',
User::$id,
$this->_post['id'][0],
User::$ip,
$_SERVER['HTTP_USER_AGENT'],
get_browser(null, true)['browser']
);
}
$ok = Util::createReport(1, 17, $this->_post['id'][0], '[Outdated Comment] '.$this->_post['reason']);
if ($ok) // this one is very special; as in: completely retarded
return 'ok'; // the script expects the actual characters 'ok' not some string like "ok"
else
trigger_error('AjaxComment::handleCommentOutOfDate - failed to update comment in db', E_USER_ERROR);
return Lang::main('genericError');
return Lang::main('intError');
}
protected function handleCommentShowReplies()
protected function handleCommentShowReplies() : string
{
return Util::toJSON(!$this->_get['id'] ? [] : CommunityContent::getCommentReplies($this->_get['id']));
}
protected function handleReplyAdd()
protected function handleReplyAdd() : string
{
$this->contentType = 'text/plain';
$this->contentType = MIME_TYPE_TEXT;
if (!User::canComment())
return 'You are not allowed to reply.';
return Lang::main('cannotComment');
else if (!$this->_post['commentId'] || !DB::Aowow()->selectCell('SELECT 1 FROM ?_comments WHERE id = ?d', $this->_post['commentId']))
return Lang::main('genericError');
else if (!$this->_post['body'] || mb_strlen($this->_post['body']) < self::REPLY_LENGTH_MIN || mb_strlen($this->_post['body']) > self::REPLY_LENGTH_MAX)
return 'Your reply has '.mb_strlen($this->_post['body']).' characters and must have at least '.self::REPLY_LENGTH_MIN.' and at most '.self::REPLY_LENGTH_MAX.'.';
else if (DB::Aowow()->query('INSERT INTO ?_comments (`userId`, `roles`, `body`, `date`, `replyTo`) VALUES (?d, ?d, ?, UNIX_TIMESTAMP(), ?d)', User::$id, User::$groups, $this->_post['body'], $this->_post['commentId']))
return Util::toJSON(CommunityContent::getCommentReplies($this->_post['commentId']));
else
return Lang::main('genericError');
if (!$this->_post['commentId'] || !DB::Aowow()->selectCell('SELECT 1 FROM ?_comments WHERE id = ?d', $this->_post['commentId']))
{
trigger_error('AjaxComment::handleReplyAdd - comment #'.$this->_post['commentId'].' does not exist', E_USER_ERROR);
return Lang::main('intError');
}
protected function handleReplyEdit()
if (!$this->_post['body'] || mb_strlen($this->_post['body']) < self::REPLY_LENGTH_MIN || mb_strlen($this->_post['body']) > self::REPLY_LENGTH_MAX)
return Lang::main('textLength', [mb_strlen($this->_post['body']), self::REPLY_LENGTH_MIN, self::REPLY_LENGTH_MAX]);
if (DB::Aowow()->query('INSERT INTO ?_comments (`userId`, `roles`, `body`, `date`, `replyTo`) VALUES (?d, ?d, ?, UNIX_TIMESTAMP(), ?d)', User::$id, User::$groups, $this->_post['body'], $this->_post['commentId']))
return Util::toJSON(CommunityContent::getCommentReplies($this->_post['commentId']));
trigger_error('AjaxComment::handleReplyAdd - write to db failed', E_USER_ERROR);
return Lang::main('intError');
}
protected function handleReplyEdit() : string
{
$this->contentType = 'text/plain';
$this->contentType = MIME_TYPE_TEXT;
if (!User::canComment())
return 'You are not allowed to reply.';
return Lang::main('cannotComment');
else if (!$this->_post['replyId'] || !$this->_post['commentId'])
return Lang::main('genericError');
if ((!$this->_post['replyId'] || !$this->_post['commentId']) && DB::Aowow()->selectCell('SELECT COUNT(1) FROM ?_comments WHERE id IN (?a)', [$this->_post['replyId'], $this->_post['commentId']]))
{
trigger_error('AjaxComment::handleReplyEdit - comment #'.$this->_post['commentId'].' or reply #'.$this->_post['replyId'].' does not exist', E_USER_ERROR);
return Lang::main('intError');
}
else if (!$this->_post['body'] || mb_strlen($this->_post['body']) < self::REPLY_LENGTH_MIN || mb_strlen($this->_post['body']) > self::REPLY_LENGTH_MAX)
return 'Your reply has '.mb_strlen($this->_post['body']).' characters and must have at least '.self::REPLY_LENGTH_MIN.' and at most '.self::REPLY_LENGTH_MAX.'.';
if (!$this->_post['body'] || mb_strlen($this->_post['body']) < self::REPLY_LENGTH_MIN || mb_strlen($this->_post['body']) > self::REPLY_LENGTH_MAX)
return Lang::main('textLength', [mb_strlen($this->_post['body']), self::REPLY_LENGTH_MIN, self::REPLY_LENGTH_MAX]);
if (DB::Aowow()->query('UPDATE ?_comments SET body = ?, editUserId = ?d, editDate = UNIX_TIMESTAMP(), editCount = editCount + 1 WHERE id = ?d AND replyTo = ?d{ AND userId = ?d}',
$this->_post['body'], User::$id, $this->_post['replyId'], $this->_post['commentId'], User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id))
return Util::toJSON(CommunityContent::getCommentReplies($this->_post['commentId']));
else
return Lang::main('genericError');
trigger_error('AjaxComment::handleReplyEdit - write to db failed', E_USER_ERROR);
return Lang::main('intError');
}
protected function handleReplyDetach()
protected function handleReplyDetach() : void
{
if (!User::isInGroup(U_GROUP_MODERATOR) || !$this->_post['id'])
if (!$this->_post['id'] || !User::isInGroup(U_GROUP_MODERATOR))
{
trigger_error('AjaxComment::handleReplyDetach - commentId empty or user #'.User::$id.' not moderator', E_USER_ERROR);
return;
}
DB::Aowow()->query('UPDATE ?_comments c1, ?_comments c2 SET c1.replyTo = 0, c1.type = c2.type, c1.typeId = c2.typeId WHERE c1.replyTo = c2.id AND c1.id = ?d', $this->_post['id'][0]);
}
protected function handleReplyDelete()
protected function handleReplyDelete() : void
{
if (!User::$id || !$this->_post['id'])
{
trigger_error('AjaxComment::handleReplyDelete - commentId empty or user not logged in', E_USER_ERROR);
return;
}
if (DB::Aowow()->query('DELETE FROM ?_comments WHERE id = ?d{ AND userId = ?d}', $this->_post['id'][0], User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id))
DB::Aowow()->query('DELETE FROM ?_comments_rates WHERE commentId = ?d', $this->_post['id'][0]);
DB::Aowow()->query('DELETE FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d', RATING_COMMENT, $this->_post['id'][0]);
else
trigger_error('AjaxComment::handleReplyDelete - deleting comment #'.$this->_post['id'][0].' by user #'.User::$id.' from db failed', E_USER_ERROR);
}
protected function handleReplyFlag()
protected function handleReplyFlag() : void
{
if (!User::$id || !$this->_post['id'])
{
trigger_error('AjaxComment::handleReplyFlag - commentId empty or user not logged in', E_USER_ERROR);
return;
DB::Aowow()->query(
'INSERT INTO ?_reports (userId, mode, reason, subject, ip, description, userAgent, appName) VALUES (?d, 1, 19, ?d, ?, "<automated commentreply report>", ?, ?)',
User::$id,
$this->_post['id'][0],
User::$ip,
$_SERVER['HTTP_USER_AGENT'],
get_browser(null, true)['browser']
);
}
protected function handleReplyUpvote()
Util::createReport(1, 19, $this->_post['id'][0], '[General Reply Report]');
}
protected function handleReplyUpvote() : void
{
if (!$this->_post['id'] || !User::canUpvote())
{
trigger_error('AjaxComment::handleReplyUpvote - commentId empty or user not allowed to vote', E_USER_ERROR);
return;
}
$owner = DB::Aowow()->selectCell('SELECT userId FROM ?_comments WHERE id = ?d', $this->_post['id'][0]);
if (!$owner)
{
trigger_error('AjaxComment::handleReplyUpvote - comment #'.$this->_post['id'][0].' not found in db', E_USER_ERROR);
return;
}
$ok = DB::Aowow()->query(
'INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)',
'INSERT INTO ?_user_ratings (`type`, `entry`, `userId`, `value`) VALUES (?d, ?d, ?d, ?d)',
RATING_COMMENT,
$this->_post['id'][0],
User::$id,
User::canSupervote() ? 2 : 1
@@ -373,19 +435,28 @@ class AjaxComment extends AjaxHandler
Util::gainSiteReputation($owner, SITEREP_ACTION_UPVOTED, ['id' => $this->_post['id'][0], 'voterId' => User::$id]);
User::decrementDailyVotes();
}
else
trigger_error('AjaxComment::handleReplyUpvote - write to db failed', E_USER_ERROR);
}
protected function handleReplyDownvote()
protected function handleReplyDownvote() : void
{
if (!$this->_post['id'] || !User::canDownvote())
{
trigger_error('AjaxComment::handleReplyDownvote - commentId empty or user not allowed to vote', E_USER_ERROR);
return;
}
$owner = DB::Aowow()->selectCell('SELECT userId FROM ?_comments WHERE id = ?d', $this->_post['id'][0]);
if (!$owner)
{
trigger_error('AjaxComment::handleReplyDownvote - comment #'.$this->_post['id'][0].' not found in db', E_USER_ERROR);
return;
}
$ok = DB::Aowow()->query(
'INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)',
'INSERT INTO ?_user_ratings (`type`, `entry`, `userId`, `value`) VALUES (?d, ?d, ?d, ?d)',
RATING_COMMENT,
$this->_post['id'][0],
User::$id,
User::canSupervote() ? -2 : -1
@@ -396,15 +467,9 @@ class AjaxComment extends AjaxHandler
Util::gainSiteReputation($owner, SITEREP_ACTION_DOWNVOTED, ['id' => $this->_post['id'][0], 'voterId' => User::$id]);
User::decrementDailyVotes();
}
else
trigger_error('AjaxComment::handleReplyDownvote - write to db failed', E_USER_ERROR);
}
}
protected function checkId($val)
{
// expecting id-list
if (preg_match('/\d+(,\d+)*/', $val))
return array_map('intVal', explode(',', $val));
return null;
}
}
?>

View File

@@ -6,15 +6,15 @@ if (!defined('AOWOW_REVISION'))
class AjaxContactus extends AjaxHandler
{
protected $_post = array(
'mode' => [FILTER_SANITIZE_NUMBER_INT, null],
'reason' => [FILTER_SANITIZE_NUMBER_INT, null],
'ua' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'appname' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'page' => [FILTER_SANITIZE_URL, null],
'desc' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW],
'id' => [FILTER_SANITIZE_NUMBER_INT, null],
'relatedurl' => [FILTER_SANITIZE_URL, null],
'email' => [FILTER_SANITIZE_EMAIL, null]
'mode' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'reason' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'ua' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'appname' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'page' => ['filter' => FILTER_SANITIZE_URL],
'desc' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'id' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'relatedurl' => ['filter' => FILTER_SANITIZE_URL],
'email' => ['filter' => FILTER_SANITIZE_EMAIL]
);
public function __construct(array $params)
@@ -33,7 +33,7 @@ class AjaxContactus extends AjaxHandler
7: already reported
$: prints response
*/
protected function handleContactUs()
protected function handleContactUs() : string
{
$mode = $this->_post['mode'];
$rsn = $this->_post['reason'];
@@ -41,6 +41,7 @@ class AjaxContactus extends AjaxHandler
$app = $this->_post['appname'];
$url = $this->_post['page'];
$desc = $this->_post['desc'];
$subj = $this->_post['id'];
$contexts = array(
[1, 2, 3, 4, 5, 6, 7, 8],
@@ -53,10 +54,16 @@ class AjaxContactus extends AjaxHandler
);
if ($mode === null || $rsn === null || $ua === null || $app === null || $url === null)
return 'required field missing';
{
trigger_error('AjaxContactus::handleContactUs - malformed contact request received', E_USER_ERROR);
return Lang::main('intError');
}
if (!isset($contexts[$mode]) || !in_array($rsn, $contexts[$mode]))
return 'mode invalid';
{
trigger_error('AjaxContactus::handleContactUs - report has invalid context (mode:'.$mode.' / reason:'.$rsn.')', E_USER_ERROR);
return Lang::main('intError');
}
if (!$desc)
return 3;
@@ -65,36 +72,22 @@ class AjaxContactus extends AjaxHandler
return 2;
if (!User::$id && !User::$ip)
return 'your ip could not be determined';
{
trigger_error('AjaxContactus::handleContactUs - could not determine IP for anonymous user', E_USER_ERROR);
return Lang::main('intError');
}
// check already reported
$field = User::$id ? 'userId' : 'ip';
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $mode, $rsn, $this->_post['id'], $field, User::$id ?: User::$ip))
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $mode, $rsn, $subj, $field, User::$id ?: User::$ip))
return 7;
$update = array(
'userId' => User::$id,
'mode' => $mode,
'reason' => $rsn,
'ip' => User::$ip,
'description' => $desc,
'userAgent' => $ua,
'appName' => $app,
'url' => $url
);
if ($_ = $this->_post['id'])
$update['subject'] = $_;
if ($_ = $this->_post['relatedurl'])
$update['relatedurl'] = $_;
if ($_ = $this->_post['email'])
$update['email'] = $_;
if (DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update)))
if (Util::createReport($mode, $rsn, $subj, $desc, $ua, $app, $url, $this->_post['relatedurl'], $this->_post['email']))
return 0;
return 'save to db unsuccessful';
trigger_error('AjaxContactus::handleContactUs - write to db failed', E_USER_ERROR);
return Lang::main('intError');
}
}
?>

View File

@@ -12,7 +12,7 @@ class AjaxCookie extends AjaxHandler
return;
$this->_get = array(
$this->params[0] => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
$params[0] => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
);
// NOW we know, what to expect and sanitize
@@ -26,12 +26,20 @@ class AjaxCookie extends AjaxHandler
0: success
$: silent error
*/
protected function handleCookie()
protected function handleCookie() : string
{
if (User::$id && $this->params && $this->_get[$this->params[0]])
{
if (DB::Aowow()->query('REPLACE INTO ?_account_cookies VALUES (?d, ?, ?)', User::$id, $this->params[0], $this->_get[$this->params[0]]))
return 0;
return '0';
else
trigger_error('AjaxCookie::handleCookie - write to db failed', E_USER_ERROR);
}
else
trigger_error('AjaxCookie::handleCookie - malformed request received', E_USER_ERROR);
return null;
return '';
}
}
?>

View File

@@ -6,12 +6,12 @@ if (!defined('AOWOW_REVISION'))
class AjaxData extends AjaxHandler
{
protected $_get = array(
'locale' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkLocale']],
't' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
'catg' => [FILTER_SANITIZE_NUMBER_INT, null],
'skill' => [FILTER_CALLBACK, ['options' => 'AjaxData::checkSkill']],
'class' => [FILTER_SANITIZE_NUMBER_INT, null],
'callback' => [FILTER_CALLBACK, ['options' => 'AjaxData::checkCallback']]
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale'],
't' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
'catg' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
'skill' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxData::checkSkill' ],
'class' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
'callback' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxData::checkCallback' ]
);
public function __construct(array $params)
@@ -28,7 +28,7 @@ class AjaxData extends AjaxHandler
/* responses
<string>
*/
protected function handleData()
protected function handleData() : string
{
$result = '';
@@ -36,9 +36,11 @@ class AjaxData extends AjaxHandler
foreach ($this->params as $set)
{
// requires valid token to hinder automated access
if ($set != 'item-scaling')
if (!$this->_get['t'] || empty($_SESSION['dataKey']) || $this->_get['t'] != $_SESSION['dataKey'])
if ($set != 'item-scaling' && (!$this->_get['t'] || empty($_SESSION['dataKey']) || $this->_get['t'] != $_SESSION['dataKey']))
{
trigger_error('AjaxData::handleData - session data key empty or expired', E_USER_ERROR);
continue;
}
switch ($set)
{
@@ -107,6 +109,7 @@ class AjaxData extends AjaxHandler
$result .= "\n\n";
break;
default:
trigger_error('AjaxData::handleData - invalid file "'.$set.'" in request', E_USER_ERROR);
break;
}
}
@@ -114,17 +117,17 @@ class AjaxData extends AjaxHandler
return $result;
}
protected function checkSkill($val)
protected static function checkSkill(string $val) : array
{
return array_intersect([171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356], explode(',', $val));
}
protected function checkCallback($val)
protected static function checkCallback(string $val) : bool
{
return substr($val, 0, 29) == '$WowheadProfiler.loadOnDemand';
return substr($val, 0, 29) === '$WowheadProfiler.loadOnDemand';
}
private function loadProfilerData($file, $catg = 'null')
private function loadProfilerData(string $file, string $catg = 'null') : string
{
$result = '';
if ($this->_get['callback'])

View File

@@ -0,0 +1,80 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AjaxEdit extends AjaxHandler
{
protected $_get = array(
'qqfile' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'guide' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
);
public function __construct(array $params)
{
parent::__construct($params);
if (!$params)
return;
if ($params[0] == 'image')
$this->handler = 'handleUpload';
else if ($params[0] == 'article') // has it's own editor page
$this->handler = null;
}
/*
success: bool
id: image enumerator
type: 3 ? png : jpg
name: old filename
error: errString
*/
protected function handleUpload() : string
{
if (!User::$id || $this->_get['guide'] != 1)
return Util::toJSON(['success' => false, 'error' => '']);
require_once('includes/libs/qqFileUploader.class.php');
$targetPath = 'static/uploads/guide/images/';
$tmpPath = 'static/uploads/temp/';
$tmpFile = User::$displayName.'-'.Type::GUIDE.'-0-'.Util::createHash(16);
$uploader = new qqFileUploader(['jpg', 'jpeg', 'png'], 10 * 1024 * 1024);
$result = $uploader->handleUpload($tmpPath, $tmpFile, true);
if (isset($result['success']))
{
$finfo = new finfo(FILEINFO_MIME);
$mime = $finfo->file($tmpPath.$result['newFilename']);
if (preg_match('/^image\/(png|jpe?g)/i', $mime, $m))
{
$i = 1; // image index
if ($files = scandir($targetPath, SCANDIR_SORT_DESCENDING))
if (rsort($files, SORT_NATURAL) && $files[0] != '.' && $files[0] != '..')
$i = explode('.', $files[0])[0] + 1;
$targetFile = $i . ($m[1] == 'png' ? '.png' : '.jpg');
// move to final location
if (!rename($tmpPath.$result['newFilename'], $targetPath.$targetFile))
return Util::toJSON(['error' => Lang::main('intError')]);
// send success
return Util::toJSON(array(
'success' => true,
'id' => $i,
'type' => $m[1] == 'png' ? 3 : 2,
'name' => $this->_get['qqfile']
));
}
return Util::toJSON(['error' => Lang::screenshot('error', 'unkFormat')]);
}
return Util::toJSON($result);
}
}
?>

View File

@@ -35,6 +35,9 @@ class AjaxFilter extends AjaxHandler
case 'achievements':
$this->filter = (new AchievementListFilter(true, $opts));
break;
case 'areatriggers':
$this->filter = (new AreaTriggerListFilter(true, $opts));
break;
case 'enchantments':
$this->filter = (new EnchantmentListFilter(true, $opts));
break;
@@ -81,7 +84,7 @@ class AjaxFilter extends AjaxHandler
$this->handler = 'handleFilter';
}
protected function handleFilter()
protected function handleFilter() : string
{
$url = '?'.$this->page;
@@ -103,5 +106,6 @@ class AjaxFilter extends AjaxHandler
// do get request
return $url;
}
}
?>

View File

@@ -0,0 +1,35 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AjaxGetdescription extends AjaxHandler
{
protected $_post = array(
'description' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']]
);
public function __construct(array $params)
{
parent::__construct($params);
if (!$params || $params[0]) // should be empty
return;
$this->handler = 'handleDescription';
}
protected function handleDescription() : string
{
$this->contentType = MIME_TYPE_TEXT;
if (!User::$id)
return '';
$desc = (new Markup($this->_post['description']))->stripTags();
return Lang::trimTextClean($desc, 120);
}
}
?>

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class AjaxGotocomment extends AjaxHandler
{
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']]
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt']
);
public function __construct(array $params)
@@ -21,15 +21,17 @@ class AjaxGotocomment extends AjaxHandler
/* responses
header()
*/
protected function handleGoToComment()
protected function handleGoToComment() : string
{
if (!$this->_get['id'])
exit; // just be blank
return '.'; // go home
if ($_ = 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']))
return '?'.Util::$typeStrings[$_['type']].'='.$_['typeId'].'#comments:id='.$_['id'].($_['id'] != $this->_get['id'] ? ':reply='.$this->_get['id'] : null);
return '?'.Type::getFileString(intVal($_['type'])).'='.$_['typeId'].'#comments:id='.$_['id'].($_['id'] != $this->_get['id'] ? ':reply='.$this->_get['id'] : null);
else
exit;
trigger_error('AjaxGotocomment::handleGoToComment - could not find comment #'.$this->get['id'], E_USER_ERROR);
return '.';
}
}

View File

@@ -0,0 +1,61 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AjaxGuide extends AjaxHandler
{
protected $_post = array(
'id' => [FILTER_SANITIZE_NUMBER_INT, null],
'rating' => [FILTER_SANITIZE_NUMBER_INT, null]
);
public function __construct(array $params)
{
parent::__construct($params);
if (!$this->params || count($this->params) != 1)
return;
$this->contentType = MIME_TYPE_TEXT;
// select handler
if ($this->params[0] == 'vote')
$this->handler = 'voteGuide';
}
protected function voteGuide() : string
{
if (!$this->_post['id'] || $this->_post['rating'] < 0 || $this->_post['rating'] > 5)
{
header('HTTP/1.0 404 Not Found', true, 404);
return '';
}
else if (!User::canUpvote() || !User::canDownvote()) // same logic as comments?
{
header('HTTP/1.0 403 Forbidden', true, 403);
return '';
}
// by id, not own, published
if ($g = DB::Aowow()->selectRow('SELECT `userId`, `cuFlags` FROM ?_guides WHERE `id` = ?d AND (`status` = ?d OR `rev` > 0)', $this->_post['id'], GUIDE_STATUS_APPROVED))
{
if ($g['cuFlags'] & GUIDE_CU_NO_RATING || $g['userId'] == User::$id)
{
header('HTTP/1.0 403 Forbidden', true, 403);
return '';
}
if (!$this->_post['rating'])
DB::Aowow()->query('DELETE FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d AND `userId` = ?d', RATING_GUIDE, $this->_post['id'], User::$id);
else
DB::Aowow()->query('REPLACE INTO ?_user_ratings VALUES (?d, ?d, ?d, ?d)', RATING_GUIDE, $this->_post['id'], User::$id, $this->_post['rating']);
$res = DB::Aowow()->selectRow('SELECT IFNULL(SUM(`value`), 0) AS `t`, IFNULL(COUNT(*), 0) AS `n` FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d', RATING_GUIDE, $this->_post['id']);
return Util::toJSON($res['n'] ? ['rating' => $res['t'] / $res['n'], 'nvotes' => $res['n']] : ['rating' => 0, 'nvotes' => 0]);
}
return Util::toJSON(['rating' => 0, 'nvotes' => 0]);
}
}
?>

View File

@@ -7,8 +7,8 @@ class AjaxGuild extends AjaxHandler
{
protected $validParams = ['resync', 'status'];
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']],
'profile' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
);
public function __construct(array $params)
@@ -35,16 +35,16 @@ class AjaxGuild extends AjaxHandler
profile: <empty> [optional, also get related chars]
return: 1
*/
protected function handleResync()
protected function handleResync() : string
{
if ($guilds = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_guild WHERE id IN (?a)', $this->_get['id']))
foreach ($guilds as $g)
Profiler::scheduleResync(TYPE_GUILD, $g['realm'], $g['realmGUID']);
Profiler::scheduleResync(Type::GUILD, $g['realm'], $g['realmGUID']);
if ($this->_get['profile'])
if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles WHERE guild IN (?a)', $this->_get['id']))
foreach ($chars as $c)
Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']);
Profiler::scheduleResync(Type::PROFILE, $c['realm'], $c['realmGUID']);
return '1';
}
@@ -72,9 +72,9 @@ class AjaxGuild extends AjaxHandler
1: char does not exist
2: armory gone
*/
protected function handleStatus()
protected function handleStatus() : string
{
$response = Profiler::resyncStatus(TYPE_GUILD, $this->_get['id']);
$response = Profiler::resyncStatus(Type::GUILD, $this->_get['id']);
return Util::toJSON($response);
}
}

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class AjaxLocale extends AjaxHandler
{
protected $_get = array(
'locale' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkLocale']]
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
);
public function __construct(array $params)
@@ -21,7 +21,7 @@ class AjaxLocale extends AjaxHandler
/* responses
header()
*/
protected function handleLocale()
protected function handleLocale() : string
{
User::setLocale($this->_get['locale']);
User::save();

View File

@@ -9,35 +9,36 @@ class AjaxProfile extends AjaxHandler
protected $validParams = ['link', 'unlink', 'pin', 'unpin', 'public', 'private', 'avatar', 'resync', 'status', 'save', 'delete', 'purge', 'summary', 'load'];
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdList']],
'items' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItemList']],
'size' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
'guild' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']],
'arena-team' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkEmptySet']],
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
'items' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxProfile::checkItemList'],
'size' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
'guild' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
'arena-team' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxProfile::checkUser' ]
);
protected $_post = array(
'name' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']],
'level' => [FILTER_SANITIZE_NUMBER_INT, null],
'class' => [FILTER_SANITIZE_NUMBER_INT, null],
'race' => [FILTER_SANITIZE_NUMBER_INT, null],
'gender' => [FILTER_SANITIZE_NUMBER_INT, null],
'nomodel' => [FILTER_SANITIZE_NUMBER_INT, null],
'talenttree1' => [FILTER_SANITIZE_NUMBER_INT, null],
'talenttree2' => [FILTER_SANITIZE_NUMBER_INT, null],
'talenttree3' => [FILTER_SANITIZE_NUMBER_INT, null],
'activespec' => [FILTER_SANITIZE_NUMBER_INT, null],
'talentbuild1' => [FILTER_SANITIZE_STRING, 0xC],// FILTER_FLAG_STRIP_LOW | *_HIGH
'glyphs1' => [FILTER_SANITIZE_STRING, 0xC],
'talentbuild2' => [FILTER_SANITIZE_STRING, 0xC],
'glyphs2' => [FILTER_SANITIZE_STRING, 0xC],
'icon' => [FILTER_SANITIZE_STRING, 0xC],
'description' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']],
'source' => [FILTER_SANITIZE_NUMBER_INT, null],
'copy' => [FILTER_SANITIZE_NUMBER_INT, null],
'public' => [FILTER_SANITIZE_NUMBER_INT, null],
'gearscore' => [FILTER_SANITIZE_NUMBER_INT, null],
'inv' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItemString', 'flags' => FILTER_REQUIRE_ARRAY]],
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext'],
'level' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'class' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'race' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'gender' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'nomodel' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'talenttree1' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'talenttree2' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'talenttree3' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'activespec' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'talentbuild1' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'glyphs1' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'talentbuild2' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'glyphs2' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'icon' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'description' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext'],
'source' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'copy' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'public' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'gearscore' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
'inv' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned', 'flags' => FILTER_REQUIRE_ARRAY],
);
public function __construct(array $params)
@@ -47,6 +48,9 @@ class AjaxProfile extends AjaxHandler
if (!$this->params)
return;
if (!CFG_PROFILER_ENABLE)
return;
switch ($this->params[0])
{
case 'unlink':
@@ -95,23 +99,39 @@ class AjaxProfile extends AjaxHandler
user: <string> [optional]
return: null
*/
protected function handleLink() // links char with account
protected function handleLink() : void // links char with account
{
if (!User::$id || empty($this->_get['id']))
{
trigger_error('AjaxProfile::handleLink - profileId empty or user not logged in', E_USER_ERROR);
return;
}
$uid = User::$id;
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']);
else if ($this->_get['user'])
{
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
{
trigger_error('AjaxProfile::handleLink - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
return;
}
}
if ($this->undo)
DB::Aowow()->query('DELETE FROM ?_account_profiles WHERE accountId = ?d AND profileId IN (?a)', $uid, $this->_get['id']);
else
{
foreach ($this->_get['id'] as $prId) // only link characters, not custom profiles
{
if ($prId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE id = ?d AND realm IS NOT NULL', $prId))
DB::Aowow()->query('INSERT IGNORE INTO ?_account_profiles VALUES (?d, ?d, 0)', $uid, $prId);
else
{
trigger_error('AjaxProfile::handleLink - profile #'.$prId.' is custom or does not exist', E_USER_ERROR);
return;
}
}
}
}
/* params
@@ -119,20 +139,27 @@ class AjaxProfile extends AjaxHandler
user: <string> [optional]
return: null
*/
protected function handlePin() // (un)favorite
protected function handlePin() : void // (un)favorite
{
if (!User::$id || empty($this->_get['id'][0]))
{
trigger_error('AjaxProfile::handlePin - profileId empty or user not logged in', E_USER_ERROR);
return;
}
$uid = User::$id;
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']);
else if ($this->_get['user'])
{
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
{
trigger_error('AjaxProfile::handlePin - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
return;
}
}
// since only one character can be pinned at a time we can reset everything
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags & ?d WHERE accountId = ?d', ~PROFILER_CU_PINNED, $uid);
// and set a single char if nesecary
// and set a single char if necessary
if (!$this->undo)
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags | ?d WHERE profileId = ?d AND accountId = ?d', PROFILER_CU_PINNED, $this->_get['id'][0], $uid);
}
@@ -142,16 +169,23 @@ class AjaxProfile extends AjaxHandler
user: <string> [optional]
return: null
*/
protected function handlePrivacy() // public visibility
protected function handlePrivacy() : void // public visibility
{
if (!User::$id || empty($this->_get['id'][0]))
{
trigger_error('AjaxProfile::handlePrivacy - profileId empty or user not logged in', E_USER_ERROR);
return;
}
$uid = User::$id;
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
$uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user']);
else if ($this->_get['user'])
{
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
{
trigger_error('AjaxProfile::handlePrivacy - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
return;
}
}
if ($this->undo)
{
@@ -170,7 +204,7 @@ class AjaxProfile extends AjaxHandler
size: <string> [optional]
return: image-header
*/
protected function handleAvatar() // image
protected function handleAvatar() : void // image
{
// something happened in the last years: those textures do not include tiny icons
$sizes = [/* 'tiny' => 15, */'small' => 18, 'medium' => 36, 'large' => 56];
@@ -178,9 +212,12 @@ class AjaxProfile extends AjaxHandler
$s = $this->_get['size'] ?: 'medium';
if (!$this->_get['id'] || !preg_match('/^([0-9]+)\.(jpg|gif)$/', $this->_get['id'][0], $matches) || !in_array($s, array_keys($sizes)))
{
trigger_error('AjaxProfile::handleAvatar - malformed request received', E_USER_ERROR);
return;
}
$this->contentType = 'image/'.$matches[2];
$this->contentType = $matches[2] == 'png' ? MIME_TYPE_PNG : MIME_TYPE_JPEG;
$id = $matches[1];
$dest = imageCreateTruecolor($sizes[$s], $sizes[$s]);
@@ -202,13 +239,13 @@ class AjaxProfile extends AjaxHandler
$src = imageCreateFromJpeg(printf($aPath, $id));
imagecopymerge($dest, $src, 0, 0, $offsetX, $offsetY, $sizes[$s], $sizes[$s], 100);
}
else
trigger_error('AjaxProfile::handleAvatar - avatar file #'.$id.' not found', E_USER_ERROR);
if ($matches[2] == 'gif')
imageGif($dest);
else
imageJpeg($dest);
return;
}
/* params
@@ -216,11 +253,15 @@ class AjaxProfile extends AjaxHandler
user: <string> [optional, not used]
return: 1
*/
protected function handleResync()
protected function handleResync() : string
{
if ($chars = DB::Aowow()->select('SELECT realm, realmGUID FROM ?_profiler_profiles WHERE id IN (?a)', $this->_get['id']))
{
foreach ($chars as $c)
Profiler::scheduleResync(TYPE_PROFILE, $c['realm'], $c['realmGUID']);
Profiler::scheduleResync(Type::PROFILE, $c['realm'], $c['realmGUID']);
}
else
trigger_error('AjaxProfile::handleResync - profiles '.implode(', ', $this->_get['id']).' not found in db', E_USER_ERROR);
return '1';
}
@@ -248,7 +289,7 @@ class AjaxProfile extends AjaxHandler
1: char does not exist
2: armory gone
*/
protected function handleStatus()
protected function handleStatus() : string
{
// roster resync for this guild was requested -> get char list
if ($this->_get['guild'])
@@ -258,7 +299,13 @@ class AjaxProfile extends AjaxHandler
else
$ids = $this->_get['id'];
$response = Profiler::resyncStatus(TYPE_PROFILE, $ids);
if (!$ids)
{
trigger_error('AjaxProfile::handleStatus - 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);
return Util::toJSON([1, [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_CHAR]]);
}
$response = Profiler::resyncStatus(Type::PROFILE, $ids);
return Util::toJSON($response);
}
@@ -270,7 +317,7 @@ class AjaxProfile extends AjaxHandler
proileId [onSuccess]
-1 [onError]
*/
protected function handleSave() // unKill a profile
protected function handleSave() : string // unKill a profile
{
// todo (med): detail check this post-data
$cuProfile = array(
@@ -324,7 +371,7 @@ class AjaxProfile extends AjaxHandler
}
else // new
{
$nProfiles = DB::Aowow()->selectCell('SELECT COUNT(*) FROM ?_profiler_profiles WHERE user = ?d AND realmGUID IS NULL', User::$id);
$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);
if ($nProfiles < 10 || User::isPremium())
if ($newId = DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a)', array_keys($cuProfile), array_values($cuProfile)))
$charId = $newId;
@@ -393,7 +440,7 @@ class AjaxProfile extends AjaxHandler
}
}
return $charId;
return (string)$charId;
}
/* params
@@ -401,10 +448,13 @@ class AjaxProfile extends AjaxHandler
return
null
*/
protected function handleDelete() // kill a profile
protected function handleDelete() : void // kill a profile
{
if (!$this->_get['id'])
if (!User::$id || !$this->_get['id'])
{
trigger_error('AjaxProfile::handleDelete - profileId empty or user not logged in', E_USER_ERROR);
return;
}
// only flag as deleted; only custom profiles
DB::Aowow()->query(
@@ -423,24 +473,27 @@ class AjaxProfile extends AjaxHandler
return
lots...
*/
protected function handleLoad()
protected function handleLoad() : string
{
// titles, achievements, characterData, talents, pets
// and some onLoad-hook to .. load it registerProfile($data)
// everything else goes through data.php .. strangely enough
if (!$this->_get['id'])
return;
{
trigger_error('AjaxProfile::handleLoad - profileId empty', E_USER_ERROR);
return '';
}
$pBase = DB::Aowow()->selectRow('SELECT pg.name AS guildname, p.* FROM ?_profiler_profiles p LEFT JOIN ?_profiler_guild pg ON pg.id = p.guild WHERE p.id = ?d', $this->_get['id'][0]);
if (!$pBase)
{
trigger_error('Profiler::handleLoad() - called with invalid profileId #'.$this->_get['id'][0], E_USER_WARNING);
return;
trigger_error('Profiler::handleLoad - called with invalid profileId #'.$this->_get['id'][0], E_USER_WARNING);
return '';
}
if (($pBase['cuFlags'] & PROFILER_CU_DELETED) && !User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return;
return '';
$rData = [];
@@ -508,7 +561,7 @@ class AjaxProfile extends AjaxHandler
{
$profile['region'] = [$rData['region'], Lang::profiler('regions', $rData['region'])];
$profile['battlegroup'] = [Profiler::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP];
$profile['realm'] = [Profiler::urlize($rData['name']), $rData['name']];
$profile['realm'] = [Profiler::urlize($rData['name'], true), $rData['name']];
}
// bookmarks
@@ -524,7 +577,7 @@ class AjaxProfile extends AjaxHandler
$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', $pBase['id']))
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))
{
foreach ($customs as $id => $cu)
{
@@ -550,28 +603,28 @@ class AjaxProfile extends AjaxHandler
{
switch ($type)
{
case TYPE_FACTION: // factionId => amount
case Type::FACTION: // factionId => amount
$profile['reputation'] = array_combine(array_keys($data), array_column($data, 'cur'));
break;
case TYPE_TITLE:
case Type::TITLE:
foreach ($data as &$d)
$d = 1;
$profile['titles'] = $data;
break;
case TYPE_QUEST:
case Type::QUEST:
foreach ($data as &$d)
$d = 1;
$profile['quests'] = $data;
break;
case TYPE_SPELL:
case Type::SPELL:
foreach ($data as &$d)
$d = 1;
$profile['spells'] = $data;
break;
case TYPE_ACHIEVEMENT:
case Type::ACHIEVEMENT:
$achievements = array_filter($data, function ($x) { return $x['max'] === null; });
$statistics = array_filter($data, function ($x) { return $x['max'] !== null; });
@@ -589,7 +642,7 @@ class AjaxProfile extends AjaxHandler
$profile['statistics'] = array_combine(array_keys($statistics), array_column($statistics, 'max'));
$profile['activity'] = $activity;
break;
case TYPE_SKILL:
case Type::SKILL:
foreach ($data as &$d)
$d = [$d['cur'], $d['max']];
@@ -691,26 +744,24 @@ class AjaxProfile extends AjaxHandler
return
null
*/
protected function handlePurge() { } // removes completion data (as uploaded by the wowhead client) Just fail silently if someone triggers this manually
protected function handlePurge() : void { } // removes completion data (as uploaded by the wowhead client) Just fail silently if someone triggers this manually
protected function checkItemList($val)
protected static function checkItemList($val) : array
{
// expecting item-list
if (preg_match('/\d+(:\d+)*/', $val))
return array_map('intval', explode(':', $val));
return array_map('intVal', explode(':', $val));
return null;
return [];
}
protected function checkItemString($val)
protected static function checkUser(string $val) : string
{
// expecting item-list
if (preg_match('/\d+(,\d+)*/', $val))
return array_map('intval', explode(',', $val));
if (User::isValidName($val))
return $val;
return null;
return '';
}
}
?>

View File

@@ -10,13 +10,15 @@ abstract class BaseType
public $error = true;
protected $templates = [];
protected $curTpl = []; // lets iterate!
protected $curTpl = [];
protected $matches = 0; // total matches unaffected by sqlLimit in config
protected $dbNames = ['Aowow']; // multiple DBs in profiler
protected $queryBase = '';
protected $queryOpts = [];
private $itrStack = [];
public static $contribute = CONTRIBUTE_ANY;
/*
@@ -57,7 +59,6 @@ abstract class BaseType
$where = [];
$linking = ' AND ';
$limit = CFG_SQL_LIMIT_DEFAULT;
$className = get_class($this);
if (!$this->queryBase || $conditions === null)
return;
@@ -257,18 +258,18 @@ abstract class BaseType
// execute query (finally)
$mtch = 0;
$rows = [];
// this is purely because of multiple realms per server
foreach ($this->dbNames as $dbIdx => $n)
{
$query = str_replace('DB_IDX', $dbIdx, $this->queryBase);
if ($rows = DB::{$n}($dbIdx)->SelectPage($mtch, $query))
{
$this->matches += $mtch;
foreach ($rows as $id => $row)
{
if (isset($this->templates[$id]))
trigger_error('guid for List already in use #'.$id, E_USER_WARNING);
trigger_error('GUID for List already in use #'.$id.'. Additional occurrence omitted!', E_USER_ERROR);
else
$this->templates[$id] = $row;
}
@@ -287,12 +288,12 @@ abstract class BaseType
public function &iterate()
{
$oldIdx = $this->id;
$this->itrStack[] = $this->id;
// reset on __construct
$this->reset();
while (list($id, $_) = each($this->templates))
foreach ($this->templates as $id => $__)
{
$this->id = $id;
$this->curTpl = &$this->templates[$id]; // do not use $tpl from each(), as we want to be referenceable
@@ -304,6 +305,7 @@ abstract class BaseType
// fforward to old index
$this->reset();
$oldIdx = array_pop($this->itrStack);
do
{
if (key($this->templates) != $oldIdx)
@@ -311,6 +313,7 @@ abstract class BaseType
$this->curTpl = current($this->templates);
$this->id = key($this->templates);
next($this->templates);
break;
}
while (next($this->templates));
@@ -328,6 +331,7 @@ abstract class BaseType
{
if (isset($this->templates[$id]))
{
unset($this->curTpl); // kill reference or strange stuff will happen
$this->curTpl = $this->templates[$id];
$this->id = $id;
return $this->templates[$id];
@@ -436,7 +440,7 @@ abstract class BaseType
'q': cssQuality [Items]
'z': zone [set when all happens in here]
'p': PvP [pvpSourceId]
's': TYPE_TITLE: side; TYPE_SPELL: skillId (yeah, double use. Ain't life just grand)
's': Type::TITLE: side; Type::SPELL: skillId (yeah, double use. Ain't life just grand)
'c': category [Spells / Quests]
'c2': subCat [Quests]
'icon': iconString
@@ -552,33 +556,41 @@ trait spawnHelper
private function createShortSpawns() // [zoneId, floor, [[x1, y1], [x2, y2], ..]] as tooltip2 if enabled by <a rel="map" ...> or anchor #map (one area, one floor, one creature, no survivors)
{
$this->spawnResult[SPAWNINFO_SHORT] = new StdClass;
// first get zone/floor with the most spawns
if ($res = DB::Aowow()->selectRow('SELECT areaId, floor FROM ?_spawns WHERE type = ?d && typeId = ?d GROUP BY areaId, floor ORDER BY count(1) DESC LIMIT 1', self::$type, $this->id))
if ($res = DB::Aowow()->selectRow('SELECT areaId, floor FROM ?_spawns WHERE type = ?d AND typeId = ?d AND posX > 0 AND posY > 0 GROUP BY areaId, floor ORDER BY count(1) DESC LIMIT 1', self::$type, $this->id))
{
// get relevant spawn points
$points = DB::Aowow()->select('SELECT posX, posY FROM ?_spawns WHERE type = ?d && typeId = ?d && areaId = ?d && floor = ?d', self::$type, $this->id, $res['areaId'], $res['floor']);
$points = DB::Aowow()->select('SELECT posX, posY FROM ?_spawns WHERE type = ?d AND typeId = ?d AND areaId = ?d AND floor = ?d AND posX > 0 AND posY > 0', self::$type, $this->id, $res['areaId'], $res['floor']);
$spawns = [];
foreach ($points as $p)
$spawns[] = [$p['posX'], $p['posY']];
$this->spawnResult[SPAWNINFO_SHORT] = [$res['areaId'], $res['floor'], $spawns];
$this->spawnResult[SPAWNINFO_SHORT]->zone = $res['areaId'];
$this->spawnResult[SPAWNINFO_SHORT]->coords = [$res['floor'] => $spawns];
}
}
private function createFullSpawns() // for display on map (objsct/npc detail page)
private function createFullSpawns() // for display on map (object/npc detail page)
{
$data = [];
$wpSum = [];
$wpIdx = 0;
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE type = ?d AND typeId = ?d", self::$type, $this->id);
$worldPos = [];
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE type = ?d AND typeId = ?d AND posX > 0 AND posY > 0", self::$type, $this->id);
if (!$spawns)
return;
if (User::isInGroup(U_GROUP_MODERATOR))
$worldPos = Game::getWorldPosForGUID(self::$type, ...array_column($spawns, 'guid'));
foreach ($spawns as $s)
{
// check, if we can attach waypoints to creature
// we will get a nice clusterfuck of dots if we do this for more GUIDs, than we have colors though
if (count($spawns) < 6 && self::$type == TYPE_NPC)
if (count($spawns) < 6 && self::$type == Type::NPC)
{
if ($wPoints = DB::Aowow()->select('SELECT * FROM ?_creature_waypoints WHERE creatureOrPath = ?d AND floor = ?d', $s['pathId'] ? -$s['pathId'] : $this->id, $s['floor']))
{
@@ -633,7 +645,62 @@ trait spawnHelper
$info[4] = Lang::game('mode').Lang::main('colon').implode(', ', $_);
}
$footer = '<span class="q2">Click to move to different floor</span>';
if (self::$type == Type::AREATRIGGER)
{
$o = Util::O2Deg($this->getField('orientation'));
$info[5] = 'Orientation'.Lang::main('colon').$o[0].'° ('.$o[1].')';
}
// guid < 0 are vehicle accessories. those are moved by moving the vehicle
if (User::isInGroup(U_GROUP_MODERATOR) && $worldPos && $s['guid'] > 0)
{
if ($points = Game::worldPosToZonePos($worldPos[$s['guid']]['mapId'], $worldPos[$s['guid']]['posX'], $worldPos[$s['guid']]['posY']))
{
$floors = [];
foreach ($points as $p)
{
if (isset(Game::$areaFloors[$p['areaId']]))
$floors[$p['areaId']][] = $p['floor'];
if (isset($menu[$p['areaId']]))
continue;
else if ($p['areaId'] == $s['areaId'])
$menu[$p['areaId']] = [$p['areaId'], '$g_zones['.$p['areaId'].']', '', null, ['class' => 'checked q0']];
else
$menu[$p['areaId']] = [$p['areaId'], '$g_zones['.$p['areaId'].']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$p['areaId'].', -1)', null, null];
}
foreach ($floors as $area => $f)
{
$menu[$area][2] = '';
$menu[$area][3] = [];
if ($menu[$area][4])
$menu[$area][4]['class'] = 'checked';
foreach ($f as $n)
{
$jsRef = $n;
if ($area != 4273) // Ulduar is weird maaaan.....
$jsRef--;
// todo: 3959 (BT) and 4075 (Sunwell) start at level 0 or something
if ($n == $s['floor'])
$menu[$area][3][] = [$jsRef, '$g_zone_areas['.$area.']['.$jsRef.']', '', null, ['class' => 'checked q0']];
else
$menu[$area][3][] = [$jsRef, '$g_zone_areas['.$area.']['.$jsRef.']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$area.', '.$n.')'];
}
}
$menu = array_values($menu);
}
if ($menu)
{
$footer = '<br /><span class="q2">Click to move displayed spawn point</span>';
array_unshift($menu, [null, "Move to..."]);
}
}
}
if ($info)
@@ -654,9 +721,22 @@ trait spawnHelper
foreach ($areas as $f => &$floor)
$floor['count'] = count($floor['coords']) - (!empty($wpSum[$a][$f]) ? $wpSum[$a][$f] : 0);
uasort($data, array($this, 'sortBySpawnCount'));
$this->spawnResult[SPAWNINFO_FULL] = $data;
}
private function sortBySpawnCount($a, $b)
{
$aCount = current($a)['count'];
$bCount = current($b)['count'];
if ($aCount == $bCount) {
return 0;
}
return ($aCount < $bCount) ? 1 : -1;
}
private function createZoneSpawns() // [zoneId1, zoneId2, ..] for locations-column in listview
{
$res = DB::Aowow()->selectCol("SELECT typeId AS ARRAY_KEY, GROUP_CONCAT(DISTINCT areaId) FROM ?_spawns WHERE type = ?d AND typeId IN (?a) GROUP BY typeId", self::$type, $this->getfoundIDs());
@@ -670,12 +750,12 @@ trait spawnHelper
$this->spawnResult[SPAWNINFO_ZONES] = $res;
}
private function createQuestSpawns() // [zoneId => [floor => [[x1, y1], [x2, y2], ..]]]
private function createQuestSpawns() // [zoneId => [floor => [[x1, y1], [x2, y2], ..]]] mapper on quest detail page
{
if (self::$type == TYPE_SOUND)
if (self::$type == Type::SOUND)
return;
$res = DB::Aowow()->select('SELECT areaId, floor, typeId, posX, posY FROM ?_spawns WHERE type = ?d && typeId IN (?a)', self::$type, $this->getFoundIDs());
$res = DB::Aowow()->select('SELECT areaId, floor, typeId, posX, posY FROM ?_spawns WHERE type = ?d AND typeId IN (?a) AND posX > 0 AND posY > 0', self::$type, $this->getFoundIDs());
$spawns = [];
foreach ($res as $data)
{
@@ -705,13 +785,13 @@ trait spawnHelper
public function getSpawns($mode)
{
// only Creatures, GOs and SoundEmitters can be spawned
if (!self::$type || !$this->getfoundIDs() || (self::$type != TYPE_NPC && self::$type != TYPE_OBJECT && self::$type != TYPE_SOUND))
if (!self::$type || !$this->getfoundIDs() || (self::$type != Type::NPC && self::$type != Type::OBJECT && self::$type != Type::SOUND && self::$type != Type::AREATRIGGER))
return [];
switch ($mode)
{
case SPAWNINFO_SHORT:
if (empty($this->spawnResult[SPAWNINFO_SHORT]))
if ($this->spawnResult[SPAWNINFO_SHORT] === null)
$this->createShortSpawns();
return $this->spawnResult[SPAWNINFO_SHORT];
@@ -762,14 +842,6 @@ trait profilerHelper
}
}
/*
roight!
just noticed, that the filters on pages originally pointed to ?filter=<pageName>
wich probably checked for correctness of inputs and redirected the correct values as a get-request
..
well, as it is now, its working .. and you never change a running system ..
*/
abstract class Filter
{
private static $wCards = ['*' => '%', '?' => '_'];
@@ -920,7 +992,7 @@ abstract class Filter
{
// doesn't need to set formData['form']; this happens in GET-step
foreach ($this->inputFields as $inp => list($type, $valid, $asArray))
foreach ($this->inputFields as $inp => [$type, $valid, $asArray])
{
if (!isset($_POST[$inp]) || $_POST[$inp] === '')
continue;
@@ -966,7 +1038,7 @@ abstract class Filter
}
$cr = $crs = $crv = [];
foreach ($this->inputFields as $inp => list($type, $valid, $asArray))
foreach ($this->inputFields as $inp => [$type, $valid, $asArray])
{
if (!isset($post[$inp]) || $post[$inp] === '')
continue;
@@ -1131,7 +1203,7 @@ abstract class Filter
else if (gettype($valid) == 'double')
$val = floatval($val);
else /* if (gettype($valid) == 'string') */
$var = strval($val);
$val = strval($val);
if ($valid == $val)
return true;
@@ -1193,9 +1265,9 @@ abstract class Filter
foreach ($parts as $p)
{
if ($p[0] == '-' && (mb_strlen($p) > 3 || $shortStr))
$sub[] = [$f, sprintf($exPH, mb_substr($p, 1)), '!'];
$sub[] = [$f, sprintf($exPH, str_replace('_', '\\_', mb_substr($p, 1))), '!'];
else if ($p[0] != '-' && (mb_strlen($p) > 2 || $shortStr))
$sub[] = [$f, sprintf($exPH, $p)];
$sub[] = [$f, sprintf($exPH, str_replace('_', '\\_', $p))];
}
// single cnd?
@@ -1274,12 +1346,17 @@ abstract class Filter
return null;
}
private function genericBooleanFlags($field, $value, $op)
private function genericBooleanFlags($field, $value, $op, $matchAny = false)
{
if ($this->int2Bool($op))
return [[$field, $value, '&'], $op ? $value : 0];
if (!$this->int2Bool($op))
return null;
if (!$op)
return [[$field, $value, '&'], 0];
else if ($matchAny)
return [[$field, $value, '&'], 0, '!'];
else
return [[$field, $value, '&'], $value];
}
private function genericString($field, $value, $strFlags)
@@ -1317,19 +1394,16 @@ abstract class Filter
protected function genericCriterion(&$cr)
{
$gen = $this->genericFilter[$cr[0]];
$gen = array_pad($this->genericFilter[$cr[0]], 4, null);
$result = null;
if(!isset($gen[2]))
$gen[2] = 0;
switch ($gen[0])
{
case FILTER_CR_NUMERIC:
$result = $this->genericNumeric($gen[1], $cr[2], $cr[1], $gen[2]);
break;
case FILTER_CR_FLAG:
$result = $this->genericBooleanFlags($gen[1], $gen[2], $cr[1]);
$result = $this->genericBooleanFlags($gen[1], $gen[2], $cr[1], $gen[3]);
break;
case FILTER_CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && $cr[1] >= 0)

View File

@@ -18,18 +18,18 @@ if (!defined('AOWOW_REVISION'))
class CommunityContent
{
private static $jsGlobals = [];
private static $subjCache = [];
private static array $jsGlobals = [];
private static array $subjCache = [];
private static $commentQuery = '
private static string $coQuery = '
SELECT
c.*,
a1.displayName AS user,
a2.displayName AS editUser,
a3.displayName AS deleteUser,
a4.displayName AS responseUser,
IFNULL(SUM(cr.value), 0) AS rating,
SUM(IF(cr.userId > 0 AND cr.userId = ?d, cr.value, 0)) AS userRating,
IFNULL(SUM(ur.value), 0) AS rating,
SUM(IF(ur.userId > 0 AND ur.userId = ?d, ur.value, 0)) AS userRating,
SUM(IF( r.userId > 0 AND r.userId = ?d, 1, 0)) AS userReported
FROM
?_comments c
@@ -42,7 +42,7 @@ class CommunityContent
LEFT JOIN
?_account a4 ON c.responseUserId = a4.id
LEFT JOIN
?_comments_rates cr ON c.id = cr.commentId
?_user_ratings ur ON c.id = ur.entry AND ur.type = ?d
LEFT JOIN
?_reports r ON r.subject = c.id AND r.mode = 1 AND r.reason = 19
WHERE
@@ -54,24 +54,41 @@ class CommunityContent
rating ASC
';
private static $previewQuery = '
private static string $ssQuery = '
SELECT s.id AS ARRAY_KEY, s.id, a.displayName AS user, s.date, s.width, s.height, s.caption, IF(s.status & ?d, 1, 0) AS "sticky", s.type, s.typeId
FROM ?_screenshots s
LEFT JOIN ?_account a ON s.userIdOwner = a.id
WHERE {s.userIdOwner = ?d AND }{s.type = ? AND }{s.typeId = ? AND }s.status & ?d AND (s.status & ?d) = 0
{ORDER BY ?# DESC}
{LIMIT ?d}
';
private static string $viQuery = '
SELECT v.id AS ARRAY_KEY, v.id, a.displayName AS user, v.date, v.videoId, v.caption, IF(v.status & ?d, 1, 0) AS "sticky", v.type, v.typeId
FROM ?_videos v
LEFT JOIN ?_account a ON v.userIdOwner = a.id
WHERE {v.userIdOwner = ?d AND }{v.type = ? AND }{v.typeId = ? AND }v.status & ?d AND (v.status & ?d) = 0
{ORDER BY ?# DESC}
{LIMIT ?d}
';
private static string $previewQuery = '
SELECT
c.id,
c.body AS preview,
c.date,
c.replyTo AS commentid,
UNIX_TIMESTAMP() - c.date AS elapsed,
IF(c.flags & ?d, 1, 0) AS deleted,
IF(c.type <> 0, c.type, c2.type) AS type,
IF(c.typeId <> 0, c.typeId, c2.typeId) AS typeId,
IFNULL(SUM(cr.value), 0) AS rating,
IFNULL(SUM(ur.value), 0) AS rating,
a.displayName AS user
FROM
?_comments c
JOIN
?_account a ON c.userId = a.id
LEFT JOIN
?_comments_rates cr ON cr.commentId = c.id AND cr.userId <> 0
?_user_ratings ur ON ur.entry = c.id AND ur.userId <> 0 AND ur.`type` = 1
LEFT JOIN
?_comments c2 ON c.replyTo = c2.id
WHERE
@@ -87,13 +104,13 @@ class CommunityContent
?d
';
private static function addSubject($type, $typeId)
private static function addSubject(int $type, int $typeId) : void
{
if (!isset(self::$subjCache[$type][$typeId]))
self::$subjCache[$type][$typeId] = 0;
}
private static function getSubjects()
private static function getSubjects() : void
{
foreach (self::$subjCache as $type => $ids)
{
@@ -101,39 +118,16 @@ class CommunityContent
if (!$_)
continue;
$cnd = [CFG_SQL_LIMIT_NONE, ['id', $_]];
switch ($type)
{
case TYPE_NPC: $obj = new CreatureList($cnd); break;
case TYPE_OBJECT: $obj = new GameobjectList($cnd); break;
case TYPE_ITEM: $obj = new ItemList($cnd); break;
case TYPE_ITEMSET: $obj = new ItemsetList($cnd); break;
case TYPE_QUEST: $obj = new QuestList($cnd); break;
case TYPE_SPELL: $obj = new SpellList($cnd); break;
case TYPE_ZONE: $obj = new ZoneList($cnd); break;
case TYPE_FACTION: $obj = new FactionList($cnd); break;
case TYPE_PET: $obj = new PetList($cnd); break;
case TYPE_ACHIEVEMENT: $obj = new AchievementList($cnd); break;
case TYPE_TITLE: $obj = new TitleList($cnd); break;
case TYPE_WORLDEVENT: $obj = new WorldEventList($cnd); break;
case TYPE_CLASS: $obj = new CharClassList($cnd); break;
case TYPE_RACE: $obj = new CharRaceList($cnd); break;
case TYPE_SKILL: $obj = new SkillList($cnd); break;
case TYPE_CURRENCY: $obj = new CurrencyList($cnd); break;
case TYPE_EMOTE: $obj = new EmoteList($cnd); break;
case TYPE_ENCHANTMENT: $obj = new EnchantmentList($cnd); break;
case TYPE_SOUND: $obj = new SoundList($cnd); break;
case TYPE_ICON: $obj = new IconList($cnd); break;
default: continue;
}
$obj = Type::newList($type, [CFG_SQL_LIMIT_NONE, ['id', $_]]);
if (!$obj)
continue;
foreach ($obj->iterate() as $id => $__)
self::$subjCache[$type][$id] = $obj->getField('name', true);
}
}
public static function getCommentPreviews($params = [], &$nFound = 0)
public static function getCommentPreviews(array $params = [], ?int &$nFound = 0, bool $dateFmt = true) : array
{
/*
purged:0, <- doesnt seem to be used anymore
@@ -166,31 +160,14 @@ class CommunityContent
$c['subject'] = self::$subjCache[$c['type']][$c['typeId']];
// format date
$c['date'] = date(Util::$dateFormatInternal, $c['date']);
$c['date'] = $dateFmt ? date(Util::$dateFormatInternal, $c['date']) : intVal($c['date']);
// remove commentid if not looking for replies
if (empty($params['replies']))
unset($c['commentid']);
// remove line breaks
$c['preview'] = strtr($c['preview'], ["\n" => ' ', "\r" => ' ']);
// limit whitespaces to one at a time
$c['preview'] = preg_replace('/\s+/', ' ', $c['preview']);
// limit previews to 100 chars + whatever it takes to make the last word full
if (mb_strlen($c['preview']) > 100)
{
$n = 0;
$b = [];
$parts = explode(' ', $c['preview']);
while ($n < 100 && $parts)
{
$_ = array_shift($parts);
$n += mb_strlen($_);
$b[] = $_;
}
$c['preview'] = implode(' ', $b).'…';
}
// format text for listview
$c['preview'] = Lang::trimTextClean($c['preview']);
}
else
{
@@ -202,13 +179,13 @@ class CommunityContent
return $comments;
}
public static function getCommentReplies($commentId, $limit = 0, &$nFound = 0)
public static function getCommentReplies(int $commentId, int $limit = 0, ?int &$nFound = 0) : array
{
$replies = [];
$query = $limit > 0 ? self::$commentQuery.' LIMIT '.$limit : self::$commentQuery;
$query = $limit > 0 ? self::$coQuery.' LIMIT '.$limit : self::$coQuery;
// get replies
$results = DB::Aowow()->selectPage($nFound, $query, User::$id, User::$id, $commentId, 0, 0, CC_FLAG_DELETED, User::$id, User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
$results = DB::Aowow()->selectPage($nFound, $query, User::$id, User::$id, RATING_COMMENT, $commentId, 0, 0, CC_FLAG_DELETED, User::$id, User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
foreach ($results as $r)
{
(new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals);
@@ -323,8 +300,7 @@ class CommunityContent
if ($pages)
{
// limit to one actually existing type each
$types = array_intersect(array_unique(array_column($pages, 'type')), array_keys(Util::$typeClasses));
foreach ($types as $t)
foreach (array_unique(array_column($pages, 'type')) as $t)
{
$ids = [];
foreach ($pages as $row)
@@ -334,11 +310,14 @@ class CommunityContent
if (!$ids)
continue;
$tClass = new Util::$typeClasses[$t](array(['id', $ids], CFG_SQL_LIMIT_NONE));
$obj = Type::newList($t, [CFG_SQL_LIMIT_NONE, ['id', $ids]]);
if (!$obj || $obj->error)
continue;
foreach ($pages as &$p)
if ($p['type'] == $t)
if ($tClass->getEntry($p['typeId']))
$p['name'] = $tClass->getField('name', true);
if ($obj->getEntry($p['typeId']))
$p['name'] = $obj->getField('name', true);
}
foreach ($pages as &$p)
@@ -359,10 +338,10 @@ class CommunityContent
return $pages;
}
private static function getComments($type, $typeId)
public static function getComments(int $type, int $typeId) : array
{
$results = DB::Aowow()->query(self::$commentQuery, User::$id, User::$id, 0, $type, $typeId, CC_FLAG_DELETED, User::$id, (int)User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
$results = DB::Aowow()->query(self::$coQuery, User::$id, User::$id, RATING_COMMENT, 0, $type, $typeId, CC_FLAG_DELETED, User::$id, (int)User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
$comments = [];
// additional informations
@@ -371,7 +350,7 @@ class CommunityContent
{
(new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals);
self::$jsGlobals[TYPE_USER][$r['userId']] = $r['userId'];
self::$jsGlobals[Type::USER][$r['userId']] = $r['userId'];
$c = array(
'commentv2' => 1, // always 1.. enables some features i guess..?
@@ -417,15 +396,9 @@ class CommunityContent
return $comments;
}
public static function getVideos($typeOrUser = 0, $typeId = 0, &$nFound = 0)
public static function getVideos(int $typeOrUser = 0, int $typeId = 0, int &$nFound = 0, bool $dateFmt = true) : array
{
$videos = DB::Aowow()->selectPage($nFound, "
SELECT v.id, a.displayName AS user, v.date, v.videoId, v.caption, IF(v.status & ?d, 1, 0) AS 'sticky', v.type, v.typeId
FROM ?_videos v
LEFT JOIN ?_account a ON v.userIdOwner = a.id
WHERE {v.userIdOwner = ?d AND }{v.type = ? AND }{v.typeId = ? AND }v.status & ?d AND (v.status & ?d) = 0
{ORDER BY ?# DESC}
{LIMIT ?d}",
$videos = DB::Aowow()->selectPage($nFound, self::$viQuery,
CC_FLAG_STICKY,
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
@@ -455,7 +428,7 @@ class CommunityContent
$v['subject'] = Lang::user('removed');
}
$v['date'] = date(Util::$dateFormatInternal, $v['date']);
$v['date'] = $dateFmt ? date(Util::$dateFormatInternal, $v['date']) : intVal($v['date']);
$v['videoType'] = 1; // always youtube
if (!$v['sticky'])
@@ -468,15 +441,9 @@ class CommunityContent
return $videos;
}
public static function getScreenshots($typeOrUser = 0, $typeId = 0, &$nFound = 0)
public static function getScreenshots(int $typeOrUser = 0, int $typeId = 0, int &$nFound = 0, bool $dateFmt = true) : array
{
$screenshots = DB::Aowow()->selectPage($nFound, "
SELECT s.id, a.displayName AS user, s.date, s.width, s.height, s.caption, IF(s.status & ?d, 1, 0) AS 'sticky', s.type, s.typeId
FROM ?_screenshots s
LEFT JOIN ?_account a ON s.userIdOwner = a.id
WHERE {s.userIdOwner = ?d AND }{s.type = ? AND }{s.typeId = ? AND }s.status & ?d AND (s.status & ?d) = 0
{ORDER BY ?# DESC}
{LIMIT ?d}",
$screenshots = DB::Aowow()->selectPage($nFound, self::$ssQuery,
CC_FLAG_STICKY,
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
@@ -506,7 +473,7 @@ class CommunityContent
$s['subject'] = Lang::user('removed');
}
$s['date'] = date(Util::$dateFormatInternal, $s['date']);
$s['date'] = $dateFmt ? date(Util::$dateFormatInternal, $s['date']) : intVal($s['date']);
if (!$s['sticky'])
unset($s['sticky']);
@@ -518,11 +485,11 @@ class CommunityContent
return $screenshots;
}
public static function getAll($type, $typeId, &$jsg)
public static function getAll(int $type, int $typeId, array &$jsg) : array
{
$result = array(
'vi' => self::getVideos($type, $typeId),
'sc' => self::getScreenshots($type, $typeId),
'ss' => self::getScreenshots($type, $typeId),
'co' => self::getComments($type, $typeId)
);
@@ -530,5 +497,10 @@ class CommunityContent
return $result;
}
public static function getJSGlobals() : array
{
return self::$jsGlobals;
}
}
?>

View File

@@ -15,6 +15,8 @@ class DB
private static $optionsCache = [];
private static $connectionCache = [];
private static $logs = [];
private static function createConnectSyntax(&$options)
{
return 'mysqli://'.$options['user'].':'.$options['pass'].'@'.$options['host'].'/'.$options['db'];
@@ -29,10 +31,10 @@ class DB
$interface = DbSimple_Generic::connect(self::createConnectSyntax($options));
if (!$interface || $interface->error)
die('Failed to connect to database.');
die('Failed to connect to database on index #'.$idx.".\n");
$interface->setErrorHandler(['DB', 'errorHandler']);
$interface->query('SET NAMES ?', 'utf8');
$interface->query('SET NAMES ?', 'utf8mb4');
if ($options['prefix'])
$interface->setIdentPrefix($options['prefix']);
@@ -47,6 +49,25 @@ class DB
self::$connectionCache[$idx] = true;
}
public static function test(array $options, ?string &$err = '') : bool
{
$defPort = ini_get('mysqli.default_port');
$port = 0;
if (strstr($options['host'], ':'))
[$options['host'], $port] = explode(':', $options['host']);
try {
$link = @mysqli_connect($options['host'], $options['user'], $options['pass'], $options['db'], $port ?: $defPort);
mysqli_close($link);
}
catch (Exception $e)
{
$err = '['.mysqli_connect_errno().'] '.mysqli_connect_error();
return false;
}
return true;
}
public static function errorHandler($message, $data)
{
if (!error_reporting())
@@ -58,6 +79,35 @@ class DB
exit;
}
public static function logger($self, $query, $trace)
{
if ($trace) // actual query
self::$logs[] = [substr(str_replace("\n", ' ', $query), 0, 200)];
else // the statistics
{
end(self::$logs);
self::$logs[key(self::$logs)][] = substr(explode(';', $query)[0], 5);
}
}
public static function getLogs()
{
$out = '<pre><table style="font-size:12;"><tr><th></th><th>Time</th><th>Query</th></tr>';
foreach (self::$logs as $i => [$l, $t])
{
$c = 'inherit';
preg_match('/(\d+)/', $t, $m);
if ($m[1] > 100)
$c = '#FFA0A0';
else if ($m[1] > 20)
$c = '#FFFFA0';
$out .= '<tr><td>'.$i.'.</td><td style="background-color:'.$c.';">'.$t.'</td><td>'.$l.'</td></tr>';
}
return Util::jsEscape($out).'</table></pre>';
}
public static function getDB($idx)
{
return self::$interfaceCache[$idx];

View File

@@ -8,34 +8,23 @@ if (!defined('AOWOW_REVISION'))
*/
define('E_AOWOW', E_ALL & ~(E_DEPRECATED | E_USER_DEPRECATED | E_STRICT));
define('JSON_AOWOW_POWER', JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
define('FILTER_FLAG_STRIP_AOWOW', FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK);
// TypeIds
define('TYPE_NPC', 1);
define('TYPE_OBJECT', 2);
define('TYPE_ITEM', 3);
define('TYPE_ITEMSET', 4);
define('TYPE_QUEST', 5);
define('TYPE_SPELL', 6);
define('TYPE_ZONE', 7);
define('TYPE_FACTION', 8);
define('TYPE_PET', 9);
define('TYPE_ACHIEVEMENT', 10);
define('TYPE_TITLE', 11);
define('TYPE_WORLDEVENT', 12);
define('TYPE_CLASS', 13);
define('TYPE_RACE', 14);
define('TYPE_SKILL', 15);
define('TYPE_CURRENCY', 17);
define('TYPE_SOUND', 19);
define('TYPE_ICON', 29);
define('TYPE_PROFILE', 100);
// internal types (not published to js)
define('TYPE_GUILD', 101);
define('TYPE_ARENA_TEAM', 102);
define('TYPE_USER', 500);
define('TYPE_EMOTE', 501);
define('TYPE_ENCHANTMENT', 502);
define('TYPE_AREATRIGGER', 503); // not for display, but indexing in ?_spawns-table
define('MIME_TYPE_TEXT', 'Content-Type: text/plain; charset=utf-8');
define('MIME_TYPE_XML', 'Content-Type: text/xml; charset=utf-8');
define('MIME_TYPE_JSON', 'Content-Type: application/x-javascript; charset=utf-8');
define('MIME_TYPE_RSS', 'Content-Type: application/rss+xml; charset=utf-8');
define('MIME_TYPE_JPEG', 'Content-Type: image/jpeg');
define('MIME_TYPE_PNG', 'Content-Type: image/png');
// shared setup strings
define('ERR_CREATE_FILE', 'could not create file at destination %s');
define('ERR_WRITE_FILE', 'could not write to file at destination %s');
define('ERR_READ_FILE', 'file %s could not be read');
define('ERR_MISSING_FILE', 'file %s not found');
define('ERR_NONE', 'created file %s');
define('ERR_MISSING_INCL', 'required function %s() could not be found at %s');
define('CACHE_TYPE_NONE', 0); // page will not be cached
define('CACHE_TYPE_PAGE', 1);
@@ -46,6 +35,11 @@ define('CACHE_TYPE_XML', 4); // only used by item
define('CACHE_MODE_FILECACHE', 0x1);
define('CACHE_MODE_MEMCACHED', 0x2);
define ('CSS_FILE', 1);
define ('CSS_STRING', 2);
define ('JS_FILE', 3);
define ('JS_STRING', 4);
define('SEARCH_TYPE_REGULAR', 0x10000000);
define('SEARCH_TYPE_OPEN', 0x20000000);
define('SEARCH_TYPE_JSON', 0x40000000);
@@ -72,7 +66,7 @@ define('ACC_BAN_COMMENT', 0x08); // cannot comment an
define('ACC_BAN_UPLOAD', 0x10); // cannot upload avatar / signature files [originally: ban from data upload]
define('ACC_BAN_SCREENSHOT', 0x20); // cannot upload screenshots
define('ACC_BAN_VIDEO', 0x40); // cannot suggest videos
// define('ACC_BAN_FORUM', 0x80); // cannot use forums [not used here]
define('ACC_BAN_GUIDE', 0x80); // cannot write a guide
// Site Reputation/Privileges
define('SITEREP_ACTION_REGISTER', 1); // Registered account
@@ -155,6 +149,7 @@ define('U_GROUP_PREMIUM_PERMISSIONS', (U_GROUP_PREMIUM|U_GROUP_STAFF|U_GRO
define('LOCALE_EN', 0);
define('LOCALE_FR', 2);
define('LOCALE_DE', 3);
define('LOCALE_CN', 4);
define('LOCALE_ES', 6);
define('LOCALE_RU', 8);
@@ -169,6 +164,10 @@ define('BUTTON_TALENT', 6);
define('BUTTON_EQUIP', 7);
define('BUTTON_PLAYLIST', 8);
define('BUTTON_RESYNC', 9);
define('BUTTON_GUIDE_REPORT', 10);
define('BUTTON_GUIDE_NEW', 11);
define('BUTTON_GUIDE_EDIT', 12);
define('BUTTON_GUIDE_LOG', 13);
// generic filter handler
define('FILTER_CR_BOOLEAN', 1);
@@ -212,6 +211,7 @@ define('PROFILEINFO_PROFILE', 0x1);
define('PROFILEINFO_CHARACTER', 0x2);
define('PROFILEINFO_GUILD', 0x10); // like &roster
define('PROFILEINFO_ARENA', 0x20);
define('PROFILEINFO_USER', 0x40);
define('SPAWNINFO_ZONES', 1); // not a mask, mutually exclusive
define('SPAWNINFO_SHORT', 2);
@@ -244,6 +244,16 @@ define('STR_LOCALIZED', 0x1);
define('STR_MATCH_EXACT', 0x2);
define('STR_ALLOW_SHORT', 0x4);
define('RATING_COMMENT', 1);
define('RATING_GUIDE', 2);
define('GUIDE_STATUS_NONE', 0);
define('GUIDE_STATUS_DRAFT', 1);
define('GUIDE_STATUS_REVIEW', 2);
define('GUIDE_STATUS_APPROVED', 3);
define('GUIDE_STATUS_REJECTED', 4);
define('GUIDE_STATUS_ARCHIVED', 5);
/*
* Game
*/
@@ -303,7 +313,12 @@ define('PROFILER_CU_DELETED', 0x04);
define('PROFILER_CU_PROFILE', 0x08);
define('PROFILER_CU_NEEDS_RESYNC', 0x10);
define('GUIDE_CU_NO_QUICKFACTS', 0x100); // merge with CC_FLAG_*
define('GUIDE_CU_NO_RATING', 0x200);
define('MAX_LEVEL', 80);
define('MAX_SKILL', 450);
define('MAX_LOCALES', 16); // technical limitation, 6 in use here
define('WOW_BUILD', 12340);
// Loot handles
@@ -460,6 +475,10 @@ define('TEAM_ALLIANCE', 0);
define('TEAM_HORDE', 1);
define('TEAM_NEUTRAL', 2);
// Lock Types
define('LOCK_TYPE_ITEM', 1);
define('LOCK_TYPE_SKILL', 2);
// Lock-Properties (also categorizes GOs)
define('LOCK_PROPERTY_FOOTLOCKER', 1);
define('LOCK_PROPERTY_HERBALISM', 2);
@@ -477,19 +496,115 @@ define('NPC_RANK_RARE_ELITE', 2);
define('NPC_RANK_BOSS', 3);
define('NPC_RANK_RARE', 4);
define('NPC_FLAG_GOSSIP', 0x00000001);
define('NPC_FLAG_QUEST_GIVER', 0x00000002);
define('NPC_FLAG_TRAINER', 0x00000010);
define('NPC_FLAG_CLASS_TRAINER', 0x00000020);
define('NPC_PROFESSION_TRAINER', 0x00000040);
define('NPC_FLAG_VENDOR', 0x00000080);
define('NPC_FLAG_VENDOR_AMMO', 0x00000100);
define('NPC_FLAG_VENDOR_FOOD', 0x00000200);
define('NPC_FLAG_VENDOR_POISON', 0x00000400);
define('NPC_FLAG_VENDOR_REAGENT', 0x00000800);
define('NPC_FLAG_REPAIRER', 0x00001000);
define('NPC_FLAG_FLIGHT_MASTER', 0x00002000);
define('NPC_FLAG_SPIRIT_HEALER', 0x00004000); // civil
define('NPC_FLAG_SPIRIT_GUIDE', 0x00008000); // battleground
define('NPC_FLAG_INNKEEPER', 0x00010000);
define('NPC_FLAG_BANKER', 0x00020000);
define('NPC_FLAG_PETITIONER', 0x00040000);
define('NPC_FLAG_GUILD_MASTER', 0x00080000);
define('NPC_FLAG_BATTLEMASTER', 0x00100000);
define('NPC_FLAG_AUCTIONEER', 0x00200000);
define('NPC_FLAG_STABLE_MASTER', 0x00400000);
define('NPC_FLAG_GUILD_BANK', 0x00800000);
define('NPC_FLAG_SPELLCLICK', 0x01000000);
define('NPC_FLAG_MAILBOX', 0x04000000);
define('UNIT_FLAG_SERVER_CONTROLLED', 0x00000001); //
define('UNIT_FLAG_NON_ATTACKABLE', 0x00000002); //
define('UNIT_FLAG_REMOVE_CLIENT_CONTROL', 0x00000004); //
define('UNIT_FLAG_PVP_ATTACKABLE', 0x00000008); // Allows to apply PvP rules to attackable state in addition to faction dependent state
define('UNIT_FLAG_RENAME', 0x00000010); //
define('UNIT_FLAG_PREPARATION', 0x00000020); // Don't take reagents for spells with SPELL_ATTR_EX5_NO_REAGENT_WHILE_PREP
define('UNIT_FLAG_UNK_6', 0x00000040); // not sure what it does, but it is needed to cast nontriggered spells in smart_scripts
define('UNIT_FLAG_NOT_ATTACKABLE_1', 0x00000080); // UNIT_FLAG_PVP_ATTACKABLE| UNIT_FLAG_NOT_ATTACKABLE_1 is NON_PVP_ATTACKABLE
define('UNIT_FLAG_IMMUNE_TO_PC', 0x00000100); // disables combat/assistance with PlayerCharacters (PC)
define('UNIT_FLAG_IMMUNE_TO_NPC', 0x00000200); // disables combat/assistance with NonPlayerCharacters (NPC)
define('UNIT_FLAG_LOOTING', 0x00000400); // Loot animation
define('UNIT_FLAG_PET_IN_COMBAT', 0x00000800); // In combat? 2.0.8
define('UNIT_FLAG_PVP', 0x00001000); // Changed in 3.0.3
define('UNIT_FLAG_SILENCED', 0x00002000); // Can't cast spells
define('UNIT_FLAG_CANNOT_SWIM', 0x00004000); // 2.0.8
define('UNIT_FLAG_UNK_15', 0x00008000); // Only Swim ('OnlySwim' from UnitFlags.cs in WPP)
define('UNIT_FLAG_UNK_16', 0x00010000); // No Attack 2 ('NoAttack2' from UnitFlags.cs in WPP)
define('UNIT_FLAG_PACIFIED', 0x00020000); // Creature will not attack
define('UNIT_FLAG_STUNNED', 0x00040000); // 3.0.3 ok
define('UNIT_FLAG_IN_COMBAT', 0x00080000); // ('AffectingCombat' from UnitFlags.cs in WPP)
define('UNIT_FLAG_TAXI_FLIGHT', 0x00100000); // Disable casting at client side spell not allowed by taxi flight (mounted?), probably used with 0x4 flag
define('UNIT_FLAG_DISARMED', 0x00200000); // 3.0.3, disable melee spells casting..., "Required melee weapon" added to melee spells tooltip.
define('UNIT_FLAG_CONFUSED', 0x00400000); // Confused.
define('UNIT_FLAG_FLEEING', 0x00800000); // ('Feared' from UnitFlags.cs in WPP)
define('UNIT_FLAG_PLAYER_CONTROLLED', 0x01000000); // Used in spell Eyes of the Beast for pet... let attack by controlled creature. Also used by Vehicles (PCV).
define('UNIT_FLAG_NOT_SELECTABLE', 0x02000000); // Can't be selected by mouse or with /target {name} command.
define('UNIT_FLAG_SKINNABLE', 0x04000000); // Skinnable
define('UNIT_FLAG_MOUNT', 0x08000000); // The client seems to handle it perfectly. Also used when making custom mounts.
define('UNIT_FLAG_UNK_28', 0x10000000); // (PreventKneelingWhenLooting from UnitFlags.cs in WPP)
define('UNIT_FLAG_UNK_29', 0x20000000); // Used in Feign Death spell or NPC will play dead. (PreventEmotes)
define('UNIT_FLAG_SHEATHE', 0x40000000); //
define('UNIT_FLAG_UNK_31', 0x80000000); //
define('UNIT_FLAG2_FEIGN_DEATH', 0x00000001); //
define('UNIT_FLAG2_UNK1', 0x00000002); // Hide unit model (show only player equip)
define('UNIT_FLAG2_IGNORE_REPUTATION', 0x00000004); //
define('UNIT_FLAG2_COMPREHEND_LANG', 0x00000008); //
define('UNIT_FLAG2_MIRROR_IMAGE', 0x00000010); //
define('UNIT_FLAG2_INSTANTLY_APPEAR_MODEL', 0x00000020); // Unit model instantly appears when summoned (does not fade in)
define('UNIT_FLAG2_FORCE_MOVEMENT', 0x00000040); //
define('UNIT_FLAG2_DISARM_OFFHAND', 0x00000080); //
define('UNIT_FLAG2_DISABLE_PRED_STATS', 0x00000100); // Player has disabled predicted stats (Used by raid frames)
define('UNIT_FLAG2_DISARM_RANGED', 0x00000400); // this does not disable ranged weapon display (maybe additional flag needed?)
define('UNIT_FLAG2_REGENERATE_POWER', 0x00000800); //
define('UNIT_FLAG2_RESTRICT_PARTY_INTERACTION', 0x1000); // Restrict interaction to party or raid
define('UNIT_FLAG2_PREVENT_SPELL_CLICK', 0x00002000); // Prevent spellclick
define('UNIT_FLAG2_ALLOW_ENEMY_INTERACT', 0x00004000); //
define('UNIT_FLAG2_DISABLE_TURN', 0x00008000); //
define('UNIT_FLAG2_UNK2', 0x00010000); //
define('UNIT_FLAG2_PLAY_DEATH_ANIM', 0x00020000); // Plays special death animation upon death
define('UNIT_FLAG2_ALLOW_CHEAT_SPELLS', 0x00040000); // allows casting spells with AttributesEx7 & SPELL_ATTR7_IS_CHEAT_SPELL
// UNIT_FIELD_BYTES_1 - idx 0 (UnitStandStateType)
define('UNIT_STAND_STATE_STAND', 0);
define('UNIT_STAND_STATE_SIT', 1);
define('UNIT_STAND_STATE_SIT_CHAIR', 2);
define('UNIT_STAND_STATE_SLEEP', 3);
define('UNIT_STAND_STATE_SIT_LOW_CHAIR', 4);
define('UNIT_STAND_STATE_SIT_MEDIUM_CHAIR', 5);
define('UNIT_STAND_STATE_SIT_HIGH_CHAIR', 6);
define('UNIT_STAND_STATE_DEAD', 7);
define('UNIT_STAND_STATE_KNEEL', 8);
define('UNIT_STAND_STATE_SUBMERGED', 9);
// UNIT_FIELD_BYTES_1 - idx 2 (UnitStandFlags)
define('UNIT_STAND_FLAGS_UNK1', 0x01);
define('UNIT_STAND_FLAGS_CREEP', 0x02);
define('UNIT_STAND_FLAGS_UNTRACKABLE', 0x04);
define('UNIT_STAND_FLAGS_UNK4', 0x08);
define('UNIT_STAND_FLAGS_UNK5', 0x10);
// UNIT_FIELD_BYTES_1 - idx 3 (UnitBytes1_Flags)
define('UNIT_BYTE1_FLAG_ALWAYS_STAND', 0x01);
define('UNIT_BYTE1_FLAG_HOVER', 0x02);
define('UNIT_BYTE1_FLAG_UNK_3', 0x04);
define('UNIT_DYNFLAG_LOOTABLE', 0x01); //
define('UNIT_DYNFLAG_TRACK_UNIT', 0x02); // Creature's location will be seen as a small dot in the minimap
define('UNIT_DYNFLAG_TAPPED', 0x04); // Makes creatures name appear grey (Lua_UnitIsTapped)
define('UNIT_DYNFLAG_TAPPED_BY_PLAYER', 0x08); // Lua_UnitIsTappedByPlayer usually used by PCVs (Player Controlled Vehicles)
define('UNIT_DYNFLAG_SPECIALINFO', 0x10); //
define('UNIT_DYNFLAG_DEAD', 0x20); // Makes the creature appear dead (this DOES NOT make the creature's name grey or not attack players).
define('UNIT_DYNFLAG_REFER_A_FRIEND', 0x40); //
define('UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST', 0x80); // Lua_UnitIsTappedByAllThreatList
// quest
define('QUEST_FLAG_STAY_ALIVE', 0x00001);
@@ -549,6 +664,20 @@ define('OBJECT_DESTRUCTIBLE_BUILDING', 33);
define('OBJECT_GUILD_BANK', 34);
define('OBJECT_TRAPDOOR', 35);
define('GO_FLAG_IN_USE', 0x0001); // Gameobject in use - Disables interaction while being animated
define('GO_FLAG_LOCKED', 0x0002); // Makes the Gameobject Locked. Requires a key, spell, or event to be opened. "Locked" appears in tooltip
define('GO_FLAG_INTERACT_COND', 0x0004); // Untargetable, cannot interact
define('GO_FLAG_TRANSPORT', 0x0008); // Gameobject can transport (boat, elevator, car)
define('GO_FLAG_NOT_SELECTABLE', 0x0010); // Not selectable (Not even in GM-mode)
define('GO_FLAG_NODESPAWN', 0x0020); // Never despawns. Typical for gameobjects with on/off state (doors for example)
define('GO_FLAG_TRIGGERED', 0x0040); // typically, summoned objects. Triggered by spell or other events
define('GO_FLAG_DAMAGED', 0x0200); // Gameobject has been siege damaged
define('GO_FLAG_DESTROYED', 0x0400); // Gameobject has been destroyed
define('GO_STATE_ACTIVE', 0); // show in world as used and not reset (closed door open)
define('GO_STATE_READY', 1); // show in world as ready (closed door close)
define('GO_STATE_ACTIVE_ALTERNATIVE', 2); // show in world as used in alt way and not reset (closed door open by cannon fire)
// InventoryType
define('INVTYPE_NON_EQUIP', 0);
define('INVTYPE_HEAD', 1);
@@ -683,7 +812,7 @@ define('ITEM_MOD_SPELL_POWER', 45);
define('ITEM_MOD_HEALTH_REGEN', 46);
define('ITEM_MOD_SPELL_PENETRATION', 47);
define('ITEM_MOD_BLOCK_VALUE', 48);
// ITEM_MOD_MASTERY_RATING, 49
// define('ITEM_MOD_MASTERY_RATING', 49);
define('ITEM_MOD_ARMOR', 50); // resistances v
define('ITEM_MOD_FIRE_RESISTANCE', 51);
define('ITEM_MOD_FROST_RESISTANCE', 52);
@@ -698,6 +827,312 @@ define('ITEM_MOD_SHADOW_POWER', 60);
define('ITEM_MOD_NATURE_POWER', 61);
define('ITEM_MOD_ARCANE_POWER', 62);
// Spell Attributes definitions
define('SPELL_ATTR0_CU_ENCHANT_PROC', 0x00000001); //
define('SPELL_ATTR0_CU_CONE_BACK', 0x00000002); //
define('SPELL_ATTR0_CU_CONE_LINE', 0x00000004); //
define('SPELL_ATTR0_CU_SHARE_DAMAGE', 0x00000008); //
define('SPELL_ATTR0_CU_NO_INITIAL_THREAT', 0x00000010); //
define('SPELL_ATTR0_CU_AURA_CC', 0x00000020); //
define('SPELL_ATTR0_CU_DONT_BREAK_STEALTH', 0x00000040); //
define('SPELL_ATTR0_CU_CAN_CRIT', 0x00000080); //
define('SPELL_ATTR0_CU_DIRECT_DAMAGE', 0x00000100); //
define('SPELL_ATTR0_CU_CHARGE', 0x00000200); //
define('SPELL_ATTR0_CU_PICKPOCKET', 0x00000400); //
define('SPELL_ATTR0_CU_ROLLING_PERIODIC', 0x00000800); //
define('SPELL_ATTR0_CU_NEGATIVE_EFF0', 0x00001000); //
define('SPELL_ATTR0_CU_NEGATIVE_EFF1', 0x00002000); //
define('SPELL_ATTR0_CU_NEGATIVE_EFF2', 0x00004000); //
define('SPELL_ATTR0_CU_IGNORE_ARMOR', 0x00008000); //
define('SPELL_ATTR0_CU_REQ_TARGET_FACING_CASTER', 0x00010000); //
define('SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET', 0x00020000); //
define('SPELL_ATTR0_CU_ALLOW_INFLIGHT_TARGET', 0x00040000); //
define('SPELL_ATTR0_CU_NEEDS_AMMO_DATA', 0x00080000); //
define('SPELL_ATTR0_CU_BINARY_SPELL', 0x00100000); //
define('SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC', 0x00200000); //
define('SPELL_ATTR0_CU_LIQUID_AURA', 0x00400000); //
define('SPELL_ATTR0_CU_NEGATIVE', SPELL_ATTR0_CU_NEGATIVE_EFF0 | SPELL_ATTR0_CU_NEGATIVE_EFF1 | SPELL_ATTR0_CU_NEGATIVE_EFF2); //
define('SPELL_ATTR0_UNK0', 0x00000001); // Unknown attribute 0@Attr0
define('SPELL_ATTR0_REQ_AMMO', 0x00000002); // Treat as ranged attack DESCRIPTION Use ammo, ranged attack range modifiers, ranged haste, etc.
define('SPELL_ATTR0_ON_NEXT_SWING', 0x00000004); // On next melee (type 1) DESCRIPTION Both "on next swing" attributes have identical handling in server & client
define('SPELL_ATTR0_IS_REPLENISHMENT', 0x00000008); // Replenishment (client only)
define('SPELL_ATTR0_ABILITY', 0x00000010); // Treat as ability DESCRIPTION Cannot be reflected, not affected by cast speed modifiers, etc.
define('SPELL_ATTR0_TRADESPELL', 0x00000020); // Trade skill recipe DESCRIPTION Displayed in recipe list, not affected by cast speed modifiers
define('SPELL_ATTR0_PASSIVE', 0x00000040); // Passive spell DESCRIPTION Spell is automatically cast on self by core
define('SPELL_ATTR0_HIDDEN_CLIENTSIDE', 0x00000080); // Hidden in UI (client only) DESCRIPTION Not visible in spellbook or aura bar
define('SPELL_ATTR0_HIDE_IN_COMBAT_LOG', 0x00000100); // Hidden in combat log (client only) DESCRIPTION Spell will not appear in combat logs
define('SPELL_ATTR0_TARGET_MAINHAND_ITEM', 0x00000200); // Auto-target mainhand item (client only) DESCRIPTION Client will automatically select main-hand item as cast target
define('SPELL_ATTR0_ON_NEXT_SWING_2', 0x00000400); // On next melee (type 2) DESCRIPTION Both "on next swing" attributes have identical handling in server & client
define('SPELL_ATTR0_UNK11', 0x00000800); // Unknown attribute 11@Attr0
define('SPELL_ATTR0_DAYTIME_ONLY', 0x00001000); // Only usable during daytime (unused)
define('SPELL_ATTR0_NIGHT_ONLY', 0x00002000); // Only usable during nighttime (unused)
define('SPELL_ATTR0_INDOORS_ONLY', 0x00004000); // Only usable indoors
define('SPELL_ATTR0_OUTDOORS_ONLY', 0x00008000); // Only usable outdoors
define('SPELL_ATTR0_NOT_SHAPESHIFT', 0x00010000); // Not usable while shapeshifted
define('SPELL_ATTR0_ONLY_STEALTHED', 0x00020000); // Only usable in stealth
define('SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE', 0x00040000); // Don't shealthe weapons (client only)
define('SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION', 0x00080000); // Scale with caster level DESCRIPTION For non-player casts, scale impact and power cost with caster's level
define('SPELL_ATTR0_STOP_ATTACK_TARGET', 0x00100000); // Stop attacking after cast DESCRIPTION After casting this, the current auto-attack will be interrupted
define('SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK', 0x00200000); // Prevent physical avoidance DESCRIPTION Spell cannot be dodged, parried or blocked
define('SPELL_ATTR0_CAST_TRACK_TARGET', 0x00400000); // Automatically face target during cast (client only)
define('SPELL_ATTR0_CASTABLE_WHILE_DEAD', 0x00800000); // Can be cast while dead DESCRIPTION Spells without this flag cannot be cast by dead units in non-triggered contexts
define('SPELL_ATTR0_CASTABLE_WHILE_MOUNTED', 0x01000000); // Can be cast while mounted
define('SPELL_ATTR0_DISABLED_WHILE_ACTIVE', 0x02000000); // Cooldown starts on expiry DESCRIPTION Spell is unusable while already active, and cooldown does not begin until the effects have worn off
define('SPELL_ATTR0_NEGATIVE_1', 0x04000000); // Is negative spell DESCRIPTION Forces the spell to be treated as a negative spell
define('SPELL_ATTR0_CASTABLE_WHILE_SITTING', 0x08000000); // Can be cast while sitting
define('SPELL_ATTR0_CANT_USED_IN_COMBAT', 0x10000000); // Cannot be used in combat
define('SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY', 0x20000000); // Pierce invulnerability DESCRIPTION Allows spell to pierce invulnerability, unless the invulnerability spell also has this attribute
define('SPELL_ATTR0_HEARTBEAT_RESIST_CHECK', 0x40000000); // Periodic resistance checks DESCRIPTION Periodically re-rolls against resistance to potentially expire aura early
define('SPELL_ATTR0_CANT_CANCEL', 0x80000000); // Aura cannot be cancelled DESCRIPTION Prevents the player from voluntarily canceling a positive aura
define('SPELL_ATTR1_DISMISS_PET', 0x00000001); // Dismiss Pet on cast DESCRIPTION Without this attribute, summoning spells will fail if caster already has a pet
define('SPELL_ATTR1_DRAIN_ALL_POWER', 0x00000002); // Drain all power DESCRIPTION Ignores listed power cost and drains entire pool instead
define('SPELL_ATTR1_CHANNELED_1', 0x00000004); // Channeled (type 1) DESCRIPTION Both "channeled" attributes have identical handling in server & client
define('SPELL_ATTR1_CANT_BE_REDIRECTED', 0x00000008); // Ignore redirection effects DESCRIPTION Spell will not be attracted by SPELL_MAGNET auras (Grounding Totem)
define('SPELL_ATTR1_UNK4', 0x00000010); // Unknown attribute 4@Attr1
define('SPELL_ATTR1_NOT_BREAK_STEALTH', 0x00000020); // Does not break stealth
define('SPELL_ATTR1_CHANNELED_2', 0x00000040); // Channeled (type 2) DESCRIPTION Both "channeled" attributes have identical handling in server & client
define('SPELL_ATTR1_CANT_BE_REFLECTED', 0x00000080); // Ignore reflection effects DESCRIPTION Spell will pierce through Spell Reflection and similar
define('SPELL_ATTR1_CANT_TARGET_IN_COMBAT', 0x00000100); // Target cannot be in combat
define('SPELL_ATTR1_MELEE_COMBAT_START', 0x00000200); // Starts auto-attack (client only) DESCRIPTION Caster will begin auto-attacking the target on cast
define('SPELL_ATTR1_NO_THREAT', 0x00000400); // Does not generate threat DESCRIPTION Also does not cause target to engage
define('SPELL_ATTR1_UNK11', 0x00000800); // Unknown attribute 11@Attr1 DESCRIPTION Aura?
define('SPELL_ATTR1_IS_PICKPOCKET', 0x00001000); // Pickpocket (client only)
define('SPELL_ATTR1_FARSIGHT', 0x00002000); // Farsight aura (client only)
define('SPELL_ATTR1_CHANNEL_TRACK_TARGET', 0x00004000); // Track target while channeling DESCRIPTION While channeling, adjust facing to face target
define('SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY', 0x00008000); // Immunity cancels preapplied auras DESCRIPTION For immunity spells, cancel all auras that this spell would make you immune to when the spell is applied
define('SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE', 0x00010000); // Unaffected by school immunities DESCRIPTION Will not pierce Divine Shield, Ice Block and other full invulnerabilities
define('SPELL_ATTR1_UNAUTOCASTABLE_BY_PET', 0x00020000); // Cannot be autocast by pet
define('SPELL_ATTR1_UNK18', 0x00040000); // Unknown attribute 18@Attr1 DESCRIPTION Stun, Polymorph, Daze, Hex - CC?
define('SPELL_ATTR1_CANT_TARGET_SELF', 0x00080000); // Cannot be self-cast
define('SPELL_ATTR1_REQ_COMBO_POINTS1', 0x00100000); // Requires combo points (type 1)
define('SPELL_ATTR1_UNK21', 0x00200000); // Unknown attribute 21@Attr1
define('SPELL_ATTR1_REQ_COMBO_POINTS2', 0x00400000); // Requires combo points (type 2)
define('SPELL_ATTR1_UNK23', 0x00800000); // Unknwon attribute 23@Attr1
define('SPELL_ATTR1_IS_FISHING', 0x01000000); // Fishing (client only)
define('SPELL_ATTR1_UNK25', 0x02000000); // Unknown attribute 25@Attr1
define('SPELL_ATTR1_UNK26', 0x04000000); // Unknown attribute 26@Attr1 DESCRIPTION Related to [target=focus] and [target=mouseover] macros?
define('SPELL_ATTR1_UNK27', 0x08000000); // Unknown attribute 27@Attr1 DESCRIPTION Melee spell?
define('SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR', 0x10000000); // Hide in aura bar (client only)
define('SPELL_ATTR1_CHANNEL_DISPLAY_SPELL_NAME', 0x20000000); // Show spell name during channel (client only)
define('SPELL_ATTR1_ENABLE_AT_DODGE', 0x40000000); // Enable at dodge
define('SPELL_ATTR1_UNK31', 0x80000000); // Unknown attribute 31@Attr1
define('SPELL_ATTR2_CAN_TARGET_DEAD', 0x00000001); // Can target dead players or corpses
define('SPELL_ATTR2_UNK1', 0x00000002); // Unknown attribute 1@Attr2
define('SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS', 0x00000004); // Ignore Line of Sight
define('SPELL_ATTR2_UNK3', 0x00000008); // Ignore aura scaling
define('SPELL_ATTR2_DISPLAY_IN_STANCE_BAR', 0x00000010); // Show in stance bar (client only)
define('SPELL_ATTR2_AUTOREPEAT_FLAG', 0x00000020); // Ranged auto-attack spell
define('SPELL_ATTR2_CANT_TARGET_TAPPED', 0x00000040); // Cannot target others' tapped units DESCRIPTION Can only target untapped units, or those tapped by caster
define('SPELL_ATTR2_UNK7', 0x00000080); // Unknown attribute 7@Attr2
define('SPELL_ATTR2_UNK8', 0x00000100); // Unknown attribute 8@Attr2
define('SPELL_ATTR2_UNK9', 0x00000200); // Unknown attribute 9@Attr2
define('SPELL_ATTR2_UNK10', 0x00000400); // Unknown attribute 10@Attr2 DESCRIPTION Related to taming?
define('SPELL_ATTR2_HEALTH_FUNNEL', 0x00000800); // Health Funnel
define('SPELL_ATTR2_UNK12', 0x00001000); // Unknown attribute 12@Attr2
define('SPELL_ATTR2_PRESERVE_ENCHANT_IN_ARENA', 0x00002000); // Enchant persists when entering arena
define('SPELL_ATTR2_UNK14', 0x00004000); // Unknown attribute 14@Attr2
define('SPELL_ATTR2_UNK15', 0x00008000); // Unknown attribute 15@Attr2
define('SPELL_ATTR2_TAME_BEAST', 0x00010000); // Tame Beast
define('SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS', 0x00020000); // Don't reset swing timer DESCRIPTION Does not reset melee/ranged autoattack timer on cast
define('SPELL_ATTR2_REQ_DEAD_PET', 0x00040000); // Requires dead pet
define('SPELL_ATTR2_NOT_NEED_SHAPESHIFT', 0x00080000); // Also allow outside shapeshift DESCRIPTION Even if Stances are nonzero, allow spell to be cast outside of shapeshift (though not in a different shapeshift)
define('SPELL_ATTR2_UNK20', 0x00100000); // Unknown attribute 20@Attr2
define('SPELL_ATTR2_DAMAGE_REDUCED_SHIELD', 0x00200000); // Damage reduction ability DESCRIPTION Causes BG flags to be dropped if combined with ATTR1_DISPEL_AURAS_ON_IMMUNITY
define('SPELL_ATTR2_UNK22', 0x00400000); // Unknown attribute 22@Attr2
define('SPELL_ATTR2_IS_ARCANE_CONCENTRATION', 0x00800000); // Arcane Concentration
define('SPELL_ATTR2_UNK24', 0x01000000); // Unknown attribute 24@Attr2
define('SPELL_ATTR2_UNK25', 0x02000000); // Unknown attribute 25@Attr2
define('SPELL_ATTR2_UNAFFECTED_BY_AURA_SCHOOL_IMMUNE', 0x04000000); // Pierce aura application immunities DESCRIPTION Allow aura to be applied despite target being immune to new aura applications
define('SPELL_ATTR2_UNK27', 0x08000000); // Unknown attribute 27@Attr2
define('SPELL_ATTR2_UNK28', 0x10000000); // Unknown attribute 28@Attr2
define('SPELL_ATTR2_CANT_CRIT', 0x20000000); // Cannot critically strike
define('SPELL_ATTR2_TRIGGERED_CAN_TRIGGER_PROC', 0x40000000); // Allow triggered spell to trigger (type 1) DESCRIPTION Without this attribute, any triggered spell will be unable to trigger other auras' procs
define('SPELL_ATTR2_FOOD_BUFF', 0x80000000); // Food buff (client only)
define('SPELL_ATTR3_UNK0', 0x00000001); // Unknown attribute 0@Attr3
define('SPELL_ATTR3_IGNORE_PROC_SUBCLASS_MASK', 0x00000002); // 1 Ignores subclass mask check when checking proc
define('SPELL_ATTR3_UNK2', 0x00000004); // Unknown attribute 2@Attr3
define('SPELL_ATTR3_BLOCKABLE_SPELL', 0x00000008); // Blockable spell
define('SPELL_ATTR3_IGNORE_RESURRECTION_TIMER', 0x00000010); // Ignore resurrection timer
define('SPELL_ATTR3_UNK5', 0x00000020); // Unknown attribute 5@Attr3
define('SPELL_ATTR3_UNK6', 0x00000040); // Unknown attribute 6@Attr3
define('SPELL_ATTR3_STACK_FOR_DIFF_CASTERS', 0x00000080); // Stack separately for each caster
define('SPELL_ATTR3_ONLY_TARGET_PLAYERS', 0x00000100); // Can only target players
define('SPELL_ATTR3_TRIGGERED_CAN_TRIGGER_PROC_2', 0x00000200); // Allow triggered spell to trigger (type 2) DESCRIPTION Without this attribute, any triggered spell will be unable to trigger other auras' procs
define('SPELL_ATTR3_MAIN_HAND', 0x00000400); // Require main hand weapon
define('SPELL_ATTR3_BATTLEGROUND', 0x00000800); // Can only be cast in battleground
define('SPELL_ATTR3_ONLY_TARGET_GHOSTS', 0x00001000); // Can only target ghost players
define('SPELL_ATTR3_DONT_DISPLAY_CHANNEL_BAR', 0x00002000); // Do not display channel bar (client only)
define('SPELL_ATTR3_IS_HONORLESS_TARGET', 0x00004000); // Honorless Target
define('SPELL_ATTR3_UNK15', 0x00008000); // Unknown attribute 15@Attr3 DESCRIPTION Auto Shoot, Shoot, Throw - ranged normal attack attribute?
define('SPELL_ATTR3_CANT_TRIGGER_PROC', 0x00010000); // Cannot trigger procs
define('SPELL_ATTR3_NO_INITIAL_AGGRO', 0x00020000); // No initial aggro
define('SPELL_ATTR3_IGNORE_HIT_RESULT', 0x00040000); // Ignore hit result DESCRIPTION Spell cannot miss, or be dodged/parried/blocked
define('SPELL_ATTR3_DISABLE_PROC', 0x00080000); // Cannot trigger spells during aura proc
define('SPELL_ATTR3_DEATH_PERSISTENT', 0x00100000); // Persists through death
define('SPELL_ATTR3_UNK21', 0x00200000); // Unknown attribute 21@Attr3
define('SPELL_ATTR3_REQ_WAND', 0x00400000); // Requires equipped Wand
define('SPELL_ATTR3_UNK23', 0x00800000); // Unknown attribute 23@Attr3
define('SPELL_ATTR3_REQ_OFFHAND', 0x01000000); // Requires offhand weapon
define('SPELL_ATTR3_TREAT_AS_PERIODIC', 0x02000000); // Treat as periodic effect
define('SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED', 0x04000000); // Can trigger from triggered spells
define('SPELL_ATTR3_DRAIN_SOUL', 0x08000000); // Drain Soul
define('SPELL_ATTR3_UNK28', 0x10000000); // Unknown attribute 28@Attr3
define('SPELL_ATTR3_NO_DONE_BONUS', 0x20000000); // Damage dealt is unaffected by modifiers
define('SPELL_ATTR3_DONT_DISPLAY_RANGE', 0x40000000); // Do not show range in tooltip (client only)
define('SPELL_ATTR3_UNK31', 0x80000000); // Unknown attribute 31@Attr3
define('SPELL_ATTR4_IGNORE_RESISTANCES', 0x00000001); // Cannot be resisted
define('SPELL_ATTR4_PROC_ONLY_ON_CASTER', 0x00000002); // Only proc on self-cast
define('SPELL_ATTR4_FADES_WHILE_LOGGED_OUT', 0x00000004); // Buff expires while offline DESCRIPTION Debuffs (except Resurrection Sickness) will automatically do this
define('SPELL_ATTR4_UNK3', 0x00000008); // Unknown attribute 3@Attr4
define('SPELL_ATTR4_UNK4', 0x00000010); // Treat as delayed spell
define('SPELL_ATTR4_UNK5', 0x00000020); // Unknown attribute 5@Attr4
define('SPELL_ATTR4_NOT_STEALABLE', 0x00000040); // Aura cannot be stolen
define('SPELL_ATTR4_CAN_CAST_WHILE_CASTING', 0x00000080); // Can be cast while casting DESCRIPTION Ignores already in-progress cast and still casts
define('SPELL_ATTR4_FIXED_DAMAGE', 0x00000100); // Deals fixed damage
define('SPELL_ATTR4_TRIGGER_ACTIVATE', 0x00000200); // Spell is initially disabled (client only)
define('SPELL_ATTR4_SPELL_VS_EXTEND_COST', 0x00000400); // Attack speed modifies cost DESCRIPTION Adds 10 to power cost for each 1s of weapon speed
define('SPELL_ATTR4_UNK11', 0x00000800); // Unknown attribute 11@Attr4
define('SPELL_ATTR4_UNK12', 0x00001000); // Unknown attribute 12@Attr4
define('SPELL_ATTR4_UNK13', 0x00002000); // Unknown attribute 13@Attr4
define('SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS', 0x00004000); // Damage does not break auras
define('SPELL_ATTR4_UNK15', 0x00008000); // Unknown attribute 15@Attr4
define('SPELL_ATTR4_NOT_USABLE_IN_ARENA', 0x00010000); // Not usable in arena DESCRIPTION Makes spell unusable despite CD <= 10min
define('SPELL_ATTR4_USABLE_IN_ARENA', 0x00020000); // Usable in arena DESCRIPTION Makes spell usable despite CD > 10min
define('SPELL_ATTR4_AREA_TARGET_CHAIN', 0x00040000); // Chain area targets DESCRIPTION [NYI] Hits area targets over time instead of all at once
define('SPELL_ATTR4_UNK19', 0x00080000); // Unknown attribute 19@Attr4
define('SPELL_ATTR4_NOT_CHECK_SELFCAST_POWER', 0x00100000); // Allow self-cast to override stronger aura (client only)
define('SPELL_ATTR4_UNK21', 0x00200000); // Keep when entering arena
define('SPELL_ATTR4_UNK22', 0x00400000); // Unknown attribute 22@Attr4
define('SPELL_ATTR4_CANT_TRIGGER_ITEM_SPELLS', 0x00800000); // Cannot trigger item spells
define('SPELL_ATTR4_UNK24', 0x01000000); // Unknown attribute 24@Attr4 DESCRIPTION Shoot-type spell?
define('SPELL_ATTR4_IS_PET_SCALING', 0x02000000); // Pet Scaling aura
define('SPELL_ATTR4_CAST_ONLY_IN_OUTLAND', 0x04000000); // Only in Outland/Northrend
define('SPELL_ATTR4_INHERIT_CRIT_FROM_AURA', 0x08000000); // Inherit critical chance from triggering aura
define('SPELL_ATTR4_UNK28', 0x10000000); // Unknown attribute 28@Attr4
define('SPELL_ATTR4_UNK29', 0x20000000); // Unknown attribute 29@Attr4
define('SPELL_ATTR4_UNK30', 0x40000000); // Unknown attribute 30@Attr4
define('SPELL_ATTR4_UNK31', 0x80000000); // Unknown attribute 31@Attr4
define('SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING', 0x00000001); // Can be channeled while moving
define('SPELL_ATTR5_NO_REAGENT_WHILE_PREP', 0x00000002); // No reagents during arena preparation
define('SPELL_ATTR5_REMOVE_ON_ARENA_ENTER', 0x00000004); // Remove when entering arena DESCRIPTION Force this aura to be removed on entering arena, regardless of other properties
define('SPELL_ATTR5_USABLE_WHILE_STUNNED', 0x00000008); // Usable while stunned
define('SPELL_ATTR5_UNK4', 0x00000010); // Unknown attribute 4@Attr5
define('SPELL_ATTR5_SINGLE_TARGET_SPELL', 0x00000020); // Single-target aura DESCRIPTION Remove previous application to another unit if applied
define('SPELL_ATTR5_UNK6', 0x00000040); // Unknown attribute 6@Attr5
define('SPELL_ATTR5_UNK7', 0x00000080); // Unknown attribute 7@Attr5
define('SPELL_ATTR5_UNK8', 0x00000100); // Unknown attribute 8@Attr5
define('SPELL_ATTR5_START_PERIODIC_AT_APPLY', 0x00000200); // Immediately do periodic tick on apply
define('SPELL_ATTR5_HIDE_DURATION', 0x00000400); // Do not send aura duration to client
define('SPELL_ATTR5_ALLOW_TARGET_OF_TARGET_AS_TARGET', 0x00000800); // Auto-target target of target (client only)
define('SPELL_ATTR5_UNK12', 0x00001000); // Unknown attribute 12@Attr5 DESCRIPTION Cleave related?
define('SPELL_ATTR5_HASTE_AFFECT_DURATION', 0x00002000); // Duration scales with Haste Rating
define('SPELL_ATTR5_UNK14', 0x00004000); // Unknown attribute 14@Attr5
define('SPELL_ATTR5_UNK15', 0x00008000); // Unknown attribute 15@Attr5 DESCRIPTION Related to multi-target spells?
define('SPELL_ATTR5_UNK16', 0x00010000); // Unknown attribute 16@Attr5
define('SPELL_ATTR5_USABLE_WHILE_FEARED', 0x00020000); // Usable while feared
define('SPELL_ATTR5_USABLE_WHILE_CONFUSED', 0x00040000); // Usable while confused
define('SPELL_ATTR5_DONT_TURN_DURING_CAST', 0x00080000); // Do not auto-turn while casting
define('SPELL_ATTR5_UNK20', 0x00100000); // Unknown attribute 20@Attr5
define('SPELL_ATTR5_UNK21', 0x00200000); // Unknown attribute 21@Attr5
define('SPELL_ATTR5_UNK22', 0x00400000); // Unknown attribute 22@Attr5
define('SPELL_ATTR5_UNK23', 0x00800000); // Unknown attribute 23@Attr5
define('SPELL_ATTR5_UNK24', 0x01000000); // Unknown attribute 24@Attr5
define('SPELL_ATTR5_UNK25', 0x02000000); // Unknown attribute 25@Attr5
define('SPELL_ATTR5_SKIP_CHECKCAST_LOS_CHECK', 0x04000000); // Ignore line of sight checks
define('SPELL_ATTR5_DONT_SHOW_AURA_IF_SELF_CAST', 0x08000000); // Don't show aura if self-cast (client only)
define('SPELL_ATTR5_DONT_SHOW_AURA_IF_NOT_SELF_CAST', 0x10000000); // Don't show aura unless self-cast (client only)
define('SPELL_ATTR5_UNK29', 0x20000000); // Unknown attribute 29@Attr5
define('SPELL_ATTR5_UNK30', 0x40000000); // Unknown attribute 30@Attr5
define('SPELL_ATTR5_UNK31', 0x80000000); // Unknown attribute 31@Attr5 DESCRIPTION Forces nearby enemies to attack caster?
define('SPELL_ATTR6_DONT_DISPLAY_COOLDOWN', 0x00000001); // Don't display cooldown (client only)
define('SPELL_ATTR6_ONLY_IN_ARENA', 0x00000002); // Only usable in arena
define('SPELL_ATTR6_IGNORE_CASTER_AURAS', 0x00000004); // Ignore all preventing caster auras
define('SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG', 0x00000008); // Ignore immunity flags when assisting
define('SPELL_ATTR6_UNK4', 0x00000010); // Unknown attribute 4@Attr6
define('SPELL_ATTR6_DONT_CONSUME_PROC_CHARGES', 0x00000020); // Don't consume proc charges
define('SPELL_ATTR6_USE_SPELL_CAST_EVENT', 0x00000040); // Generate spell_cast event instead of aura_start (client only)
define('SPELL_ATTR6_UNK7', 0x00000080); // Unknown attribute 7@Attr6
define('SPELL_ATTR6_CANT_TARGET_CROWD_CONTROLLED', 0x00000100); // Do not implicitly target in CC DESCRIPTION Implicit targeting (chaining and area targeting) will not impact crowd controlled targets
define('SPELL_ATTR6_UNK9', 0x00000200); // Unknown attribute 9@Attr6
define('SPELL_ATTR6_CAN_TARGET_POSSESSED_FRIENDS', 0x00000400); // Can target possessed friends DESCRIPTION [NYI]
define('SPELL_ATTR6_NOT_IN_RAID_INSTANCE', 0x00000800); // Unusable in raid instances
define('SPELL_ATTR6_CASTABLE_WHILE_ON_VEHICLE', 0x00001000); // Castable while caster is on vehicle
define('SPELL_ATTR6_CAN_TARGET_INVISIBLE', 0x00002000); // Can target invisible units
define('SPELL_ATTR6_UNK14', 0x00004000); // Unknown attribute 14@Attr6
define('SPELL_ATTR6_UNK15', 0x00008000); // Unknown attribute 15@Attr6
define('SPELL_ATTR6_UNK16', 0x00010000); // Unknown attribute 16@Attr6
define('SPELL_ATTR6_UNK17', 0x00020000); // Unknown attribute 17@Attr6 DESCRIPTION Mount related?
define('SPELL_ATTR6_CAST_BY_CHARMER', 0x00040000); // Spell is cast by charmer DESCRIPTION Client will prevent casting if not possessed, charmer will be caster for all intents and purposes
define('SPELL_ATTR6_UNK19', 0x00080000); // Unknown attribute 19@Attr6
define('SPELL_ATTR6_ONLY_VISIBLE_TO_CASTER', 0x00100000); // Only visible to caster (client only)
define('SPELL_ATTR6_CLIENT_UI_TARGET_EFFECTS', 0x00200000); // Client UI target effects (client only)
define('SPELL_ATTR6_UNK22', 0x00400000); // Unknown attribute 22@Attr6
define('SPELL_ATTR6_UNK23', 0x00800000); // Unknown attribute 23@Attr6
define('SPELL_ATTR6_CAN_TARGET_UNTARGETABLE', 0x01000000); // Can target untargetable units
define('SPELL_ATTR6_NOT_RESET_SWING_IF_INSTANT', 0x02000000); // Do not reset swing timer if cast time is instant
define('SPELL_ATTR6_UNK26', 0x04000000); // Unknown attribute 26@Attr6 DESCRIPTION Player castable buff?
define('SPELL_ATTR6_LIMIT_PCT_HEALING_MODS', 0x08000000); // Limit applicable %healing modifiers DESCRIPTION This prevents certain healing modifiers from applying - see implementation if you really care about details
define('SPELL_ATTR6_UNK28', 0x10000000); // Unknown attribute 28@Attr6 DESCRIPTION Death grip?
define('SPELL_ATTR6_LIMIT_PCT_DAMAGE_MODS', 0x20000000); // Limit applicable %damage modifiers DESCRIPTION This prevents certain damage modifiers from applying - see implementation if you really care about details
define('SPELL_ATTR6_UNK30', 0x40000000); // Unknown attribute 30@Attr6
define('SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS', 0x80000000); // Ignore cooldown modifiers for category cooldown
define('SPELL_ATTR7_UNK0', 0x00000001); // Unknown attribute 0@Attr7
define('SPELL_ATTR7_IGNORE_DURATION_MODS', 0x00000002); // Ignore duration modifiers
define('SPELL_ATTR7_REACTIVATE_AT_RESURRECT', 0x00000004); // Reactivate at resurrect (client only)
define('SPELL_ATTR7_IS_CHEAT_SPELL', 0x00000008); // Is cheat spell DESCRIPTION Cannot cast if caster doesn't have UnitFlag2 & UNIT_FLAG2_ALLOW_CHEAT_SPELLS
define('SPELL_ATTR7_UNK4', 0x00000010); // Unknown attribute 4@Attr7 DESCRIPTION Soulstone related?
define('SPELL_ATTR7_SUMMON_PLAYER_TOTEM', 0x00000020); // Summons player-owned totem
define('SPELL_ATTR7_NO_PUSHBACK_ON_DAMAGE', 0x00000040); // Damage dealt by this does not cause spell pushback
define('SPELL_ATTR7_UNK7', 0x00000080); // Unknown attribute 7@Attr7
define('SPELL_ATTR7_HORDE_ONLY', 0x00000100); // Horde only
define('SPELL_ATTR7_ALLIANCE_ONLY', 0x00000200); // Alliance only
define('SPELL_ATTR7_DISPEL_CHARGES', 0x00000400); // Dispel/Spellsteal remove individual charges
define('SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER', 0x00000800); // Only interrupt non-player casting
define('SPELL_ATTR7_UNK12', 0x00001000); // Unknown attribute 12@Attr7
define('SPELL_ATTR7_UNK13', 0x00002000); // Unknown attribute 13@Attr7
define('SPELL_ATTR7_UNK14', 0x00004000); // Unknown attribute 14@Attr7
define('SPELL_ATTR7_UNK15', 0x00008000); // Unknown attribute 15@Attr7 DESCRIPTION Exorcism - guaranteed crit vs families?
define('SPELL_ATTR7_CAN_RESTORE_SECONDARY_POWER', 0x00010000); // Can restore secondary power DESCRIPTION Only spells with this attribute can replenish a non-active power type
define('SPELL_ATTR7_UNK17', 0x00020000); // Unknown attribute 17@Attr7
define('SPELL_ATTR7_HAS_CHARGE_EFFECT', 0x00040000); // Has charge effect
define('SPELL_ATTR7_ZONE_TELEPORT', 0x00080000); // Is zone teleport
define('SPELL_ATTR7_UNK20', 0x00100000); // Unknown attribute 20@Attr7 DESCRIPTION Invulnerability related?
define('SPELL_ATTR7_UNK21', 0x00200000); // Unknown attribute 21@Attr7
define('SPELL_ATTR7_IGNORE_COLD_WEATHER_FLYING', 0x00400000); // Ignore cold weather flying restriction DESCRIPTION Set for loaner mounts, allows them to be used despite lacking required flight skill
define('SPELL_ATTR7_UNK23', 0x00800000); // Unknown attribute 23@Attr7
define('SPELL_ATTR7_UNK24', 0x01000000); // Unknown attribute 24@Attr7
define('SPELL_ATTR7_UNK25', 0x02000000); // Unknown attribute 25@Attr7
define('SPELL_ATTR7_UNK26', 0x04000000); // Unknown attribute 26@Attr7
define('SPELL_ATTR7_UNK27', 0x08000000); // Unknown attribute 27@Attr7
define('SPELL_ATTR7_CONSOLIDATED_RAID_BUFF', 0x10000000); // Consolidate in raid buff frame (client only)
define('SPELL_ATTR7_UNK29', 0x20000000); // Unknown attribute 29@Attr7
define('SPELL_ATTR7_UNK30', 0x40000000); // Unknown attribute 30@Attr7
define('SPELL_ATTR7_CLIENT_INDICATOR', 0x80000000); // Client indicator (client only)
// (some) Skill ids
define('SKILL_BLACKSMITHING', 164);
define('SKILL_LEATHERWORKING', 165);
define('SKILL_ALCHEMY', 171);
define('SKILL_HERBALISM', 182);
define('SKILL_MINING', 186);
define('SKILL_TAILORING', 197);
define('SKILL_ENGINEERING', 202);
define('SKILL_ENCHANTING', 333);
define('SKILL_SKINNING', 393);
define('SKILL_JEWELCRAFTING', 755);
define('SKILL_INSCRIPTION', 773);
define('SKILL_LOCKPICKING', 633);
// AchievementCriteriaCondition
define('ACHIEVEMENT_CRITERIA_CONDITION_NO_DEATH', 1); // reset progress on death
define('ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP', 3); // requires you to be on specific map, reset at change
@@ -886,6 +1321,314 @@ define('CND_ALIVE', 36); // target is alive:
define('CND_HP_VAL', 37); // targets absolute health: amount, operator, NULL
define('CND_HP_PCT', 38); // targets relative health: amount, operator, NULL
// TrinityCore - SmartAI
define('SAI_SRC_TYPE_CREATURE', 0);
define('SAI_SRC_TYPE_OBJECT', 1);
define('SAI_SRC_TYPE_AREATRIGGER', 2);
define('SAI_SRC_TYPE_ACTIONLIST', 9);
define('SAI_EVENT_FLAG_NO_REPEAT', 0x0001);
define('SAI_EVENT_FLAG_DIFFICULTY_0', 0x0002);
define('SAI_EVENT_FLAG_DIFFICULTY_1', 0x0004);
define('SAI_EVENT_FLAG_DIFFICULTY_2', 0x0008);
define('SAI_EVENT_FLAG_DIFFICULTY_3', 0x0010);
define('SAI_EVENT_FLAG_NO_RESET', 0x0100);
define('SAI_EVENT_FLAG_WHILE_CHARMED', 0x0200);
define('SAI_EVENT_UPDATE_IC', 0); // In combat.
define('SAI_EVENT_UPDATE_OOC', 1); // Out of combat.
define('SAI_EVENT_HEALTH_PCT', 2); // Health Percentage
define('SAI_EVENT_MANA_PCT', 3); // Mana Percentage
define('SAI_EVENT_AGGRO', 4); // On Creature Aggro
define('SAI_EVENT_KILL', 5); // On Creature Kill
define('SAI_EVENT_DEATH', 6); // On Creature Death
define('SAI_EVENT_EVADE', 7); // On Creature Evade Attack
define('SAI_EVENT_SPELLHIT', 8); // On Creature/Gameobject Spell Hit
define('SAI_EVENT_RANGE', 9); // On Target In Range
define('SAI_EVENT_OOC_LOS', 10); // On Target In Distance Out of Combat
define('SAI_EVENT_RESPAWN', 11); // On Creature/Gameobject Respawn
define('SAI_EVENT_TARGET_HEALTH_PCT', 12); // On Target Health Percentage
define('SAI_EVENT_VICTIM_CASTING', 13); // On Target Casting Spell
define('SAI_EVENT_FRIENDLY_HEALTH', 14); // On Friendly Health Deficit
define('SAI_EVENT_FRIENDLY_IS_CC', 15); //
define('SAI_EVENT_FRIENDLY_MISSING_BUFF', 16); // On Friendly Lost Buff
define('SAI_EVENT_SUMMONED_UNIT', 17); // On Creature/Gameobject Summoned Unit
define('SAI_EVENT_TARGET_MANA_PCT', 18); // On Target Mana Percentage
define('SAI_EVENT_ACCEPTED_QUEST', 19); // On Target Accepted Quest
define('SAI_EVENT_REWARD_QUEST', 20); // On Target Rewarded Quest
define('SAI_EVENT_REACHED_HOME', 21); // On Creature Reached Home
define('SAI_EVENT_RECEIVE_EMOTE', 22); // On Receive Emote.
define('SAI_EVENT_HAS_AURA', 23); // On Creature Has Aura
define('SAI_EVENT_TARGET_BUFFED', 24); // On Target Buffed With Spell
define('SAI_EVENT_RESET', 25); // After Combat, On Respawn or Spawn
define('SAI_EVENT_IC_LOS', 26); // On Target In Distance In Combat
define('SAI_EVENT_PASSENGER_BOARDED', 27); //
define('SAI_EVENT_PASSENGER_REMOVED', 28); //
define('SAI_EVENT_CHARMED', 29); // On Creature Charmed
define('SAI_EVENT_CHARMED_TARGET', 30); // On Target Charmed
define('SAI_EVENT_SPELLHIT_TARGET', 31); // On Target Spell Hit
define('SAI_EVENT_DAMAGED', 32); // On Creature Damaged
define('SAI_EVENT_DAMAGED_TARGET', 33); // On Target Damaged
define('SAI_EVENT_MOVEMENTINFORM', 34); // WAYPOINT_MOTION_TYPE = 2, POINT_MOTION_TYPE = 8
define('SAI_EVENT_SUMMON_DESPAWNED', 35); // On Summoned Unit Despawned
define('SAI_EVENT_CORPSE_REMOVED', 36); // On Creature Corpse Removed
define('SAI_EVENT_AI_INIT', 37); //
define('SAI_EVENT_DATA_SET', 38); // On Creature/Gameobject Data Set, Can be used with SMART_ACTION_SET_DATA
define('SAI_EVENT_WAYPOINT_START', 39); // On Creature Waypoint ID Started
define('SAI_EVENT_WAYPOINT_REACHED', 40); // On Creature Waypoint ID Reached
// define('SAI_EVENT_TRANSPORT_ADDPLAYER', 41); //
// define('SAI_EVENT_TRANSPORT_ADDCREATURE', 42); //
// define('SAI_EVENT_TRANSPORT_REMOVE_PLAYER', 43); //
// define('SAI_EVENT_TRANSPORT_RELOCATE', 44); //
// define('SAI_EVENT_INSTANCE_PLAYER_ENTER', 45); //
define('SAI_EVENT_AREATRIGGER_ONTRIGGER', 46); //
// define('SAI_EVENT_QUEST_ACCEPTED', 47); // On Target Quest Accepted
// define('SAI_EVENT_QUEST_OBJ_COMPLETION', 48); // On Target Quest Objective Completed
// define('SAI_EVENT_QUEST_COMPLETION', 49); // On Target Quest Completed
// define('SAI_EVENT_QUEST_REWARDED', 50); // On Target Quest Rewarded
// define('SAI_EVENT_QUEST_FAIL', 51); // On Target Quest Field
define('SAI_EVENT_TEXT_OVER', 52); // On TEXT_OVER Event Triggered After SMART_ACTION_TALK
define('SAI_EVENT_RECEIVE_HEAL', 53); // On Creature Received Healing
define('SAI_EVENT_JUST_SUMMONED', 54); // On Creature Just spawned
define('SAI_EVENT_WAYPOINT_PAUSED', 55); // On Creature Paused at Waypoint ID
define('SAI_EVENT_WAYPOINT_RESUMED', 56); // On Creature Resumed after Waypoint ID
define('SAI_EVENT_WAYPOINT_STOPPED', 57); // On Creature Stopped On Waypoint ID
define('SAI_EVENT_WAYPOINT_ENDED', 58); // On Creature Waypoint Path Ended
define('SAI_EVENT_TIMED_EVENT_TRIGGERED', 59); //
define('SAI_EVENT_UPDATE', 60); //
define('SAI_EVENT_LINK', 61); // Used to link together multiple events as a chain of events.
define('SAI_EVENT_GOSSIP_SELECT', 62); // On gossip clicked (gossip_menu_option335).
define('SAI_EVENT_JUST_CREATED', 63); //
define('SAI_EVENT_GOSSIP_HELLO', 64); // On Right-Click Creature/Gameobject that have gossip enabled.
define('SAI_EVENT_FOLLOW_COMPLETED', 65); //
define('SAI_EVENT_EVENT_PHASE_CHANGE', 66); // On event phase mask set
define('SAI_EVENT_IS_BEHIND_TARGET', 67); // On Creature is behind target.
define('SAI_EVENT_GAME_EVENT_START', 68); // On game_event started.
define('SAI_EVENT_GAME_EVENT_END', 69); // On game_event ended.
define('SAI_EVENT_GO_STATE_CHANGED', 70); //
define('SAI_EVENT_GO_EVENT_INFORM', 71); //
define('SAI_EVENT_ACTION_DONE', 72); //
define('SAI_EVENT_ON_SPELLCLICK', 73); //
define('SAI_EVENT_FRIENDLY_HEALTH_PCT', 74); //
define('SAI_EVENT_DISTANCE_CREATURE', 75); // On creature guid OR any instance of creature entry is within distance.
define('SAI_EVENT_DISTANCE_GAMEOBJECT', 76); // On gameobject guid OR any instance of gameobject entry is within distance.
define('SAI_EVENT_COUNTER_SET', 77); // If the value of specified counterID is equal to a specified value
// define('SAI_EVENT_SCENE_START', 78); // don't use on 3.3.5a
// define('SAI_EVENT_SCENE_TRIGGER', 79); // don't use on 3.3.5a
// define('SAI_EVENT_SCENE_CANCEL', 80); // don't use on 3.3.5a
// define('SAI_EVENT_SCENE_COMPLETE', 81); // don't use on 3.3.5a
define('SAI_EVENT_SUMMONED_UNIT_DIES', 82); // CreatureId(0 all), CooldownMin, CooldownMax
define('SAI_ACTION_NONE', 0); // Do nothing
define('SAI_ACTION_TALK', 1); // Param2 in Milliseconds.
define('SAI_ACTION_SET_FACTION', 2); // Sets faction to creature.
define('SAI_ACTION_MORPH_TO_ENTRY_OR_MODEL', 3); // Take DisplayID of creature (param1) OR Turn to DisplayID (param2) OR Both = 0 for Demorph
define('SAI_ACTION_SOUND', 4); // TextRange = 0 only sends sound to self, TextRange = 1 sends sound to everyone in visibility range
define('SAI_ACTION_PLAY_EMOTE', 5); // Play Emote
define('SAI_ACTION_FAIL_QUEST', 6); // Fail Quest of Target
define('SAI_ACTION_OFFER_QUEST', 7); // Add Quest to Target
define('SAI_ACTION_SET_REACT_STATE', 8); // React State. Can be Passive (0), Defensive (1), Aggressive (2), Assist (3).
define('SAI_ACTION_ACTIVATE_GOBJECT', 9); // Activate Object
define('SAI_ACTION_RANDOM_EMOTE', 10); // Play Random Emote
define('SAI_ACTION_CAST', 11); // Cast Spell ID at Target
define('SAI_ACTION_SUMMON_CREATURE', 12); // Summon Unit
define('SAI_ACTION_THREAT_SINGLE_PCT', 13); // Change Threat Percentage for Single Target
define('SAI_ACTION_THREAT_ALL_PCT', 14); // Change Threat Percentage for All Enemies
define('SAI_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS', 15); //
// define('SAI_ACTION_SET_INGAME_PHASE_ID', 16); // For 4.3.4 + only
define('SAI_ACTION_SET_EMOTE_STATE', 17); // Play Emote Continuously
define('SAI_ACTION_SET_UNIT_FLAG', 18); // Can set Multi-able flags at once
define('SAI_ACTION_REMOVE_UNIT_FLAG', 19); // Can Remove Multi-able flags at once
define('SAI_ACTION_AUTO_ATTACK', 20); // Stop or Continue Automatic Attack.
define('SAI_ACTION_ALLOW_COMBAT_MOVEMENT', 21); // Allow or Disable Combat Movement
define('SAI_ACTION_SET_EVENT_PHASE', 22); //
define('SAI_ACTION_INC_EVENT_PHASE', 23); // Set param1 OR param2 (not both). Value 0 has no effect.
define('SAI_ACTION_EVADE', 24); // Evade Incoming Attack
define('SAI_ACTION_FLEE_FOR_ASSIST', 25); // If you want the fleeing NPC to say '%s attempts to run away in fear' on flee, use 1 on param1. 0 for no message.
define('SAI_ACTION_CALL_GROUPEVENTHAPPENS', 26); //
define('SAI_ACTION_COMBAT_STOP', 27); //
define('SAI_ACTION_REMOVEAURASFROMSPELL', 28); // 0 removes all auras
define('SAI_ACTION_FOLLOW', 29); // Follow Target
define('SAI_ACTION_RANDOM_PHASE', 30); //
define('SAI_ACTION_RANDOM_PHASE_RANGE', 31); //
define('SAI_ACTION_RESET_GOBJECT', 32); // Reset Gameobject
define('SAI_ACTION_CALL_KILLEDMONSTER', 33); // This is the ID from quest_template.RequiredNpcOrGo
define('SAI_ACTION_SET_INST_DATA', 34); // Set Instance Data
// define('SAI_ACTION_SET_INST_DATA64', 35); // Set Instance Data uint64
define('SAI_ACTION_UPDATE_TEMPLATE', 36); // Updates creature_template to given entry
define('SAI_ACTION_DIE', 37); // Kill Target
define('SAI_ACTION_SET_IN_COMBAT_WITH_ZONE', 38); //
define('SAI_ACTION_CALL_FOR_HELP', 39); // If you want the NPC to say '%s calls for help!'. Use 1 on param1, 0 for no message.
define('SAI_ACTION_SET_SHEATH', 40); //
define('SAI_ACTION_FORCE_DESPAWN', 41); // Despawn Target after param1 in Milliseconds. If you want to set respawn time set param2 in seconds.
define('SAI_ACTION_SET_INVINCIBILITY_HP_LEVEL', 42); // If you use both params, only percent will be used.
define('SAI_ACTION_MOUNT_TO_ENTRY_OR_MODEL', 43); // Mount to Creature Entry (param1) OR Mount to Creature Display (param2) Or both = 0 for Unmount
define('SAI_ACTION_SET_INGAME_PHASE_MASK', 44); //
define('SAI_ACTION_SET_DATA', 45); // Set Data For Target, can be used with SMART_EVENT_DATA_SET
define('SAI_ACTION_ATTACK_STOP', 46); //
define('SAI_ACTION_SET_VISIBILITY', 47); // Makes creature Visible = 1 or Invisible = 0
define('SAI_ACTION_SET_ACTIVE', 48); //
define('SAI_ACTION_ATTACK_START', 49); // Allows basic melee swings to creature.
define('SAI_ACTION_SUMMON_GO', 50); // Spawns Gameobject, use target_type to set spawn position.
define('SAI_ACTION_KILL_UNIT', 51); // Kills Creature.
define('SAI_ACTION_ACTIVATE_TAXI', 52); // Sends player to flight path. You have to be close to Flight Master, which gives Taxi ID you need.
define('SAI_ACTION_WP_START', 53); // Creature starts Waypoint Movement. Use waypoints table to create movement.
define('SAI_ACTION_WP_PAUSE', 54); // Creature pauses its Waypoint Movement for given time.
define('SAI_ACTION_WP_STOP', 55); // Creature stops its Waypoint Movement.
define('SAI_ACTION_ADD_ITEM', 56); // Adds item(s) to player.
define('SAI_ACTION_REMOVE_ITEM', 57); // Removes item(s) from player.
define('SAI_ACTION_INSTALL_AI_TEMPLATE', 58); //
define('SAI_ACTION_SET_RUN', 59); //
define('SAI_ACTION_SET_DISABLE_GRAVITY', 60); // Only works for creatures with inhabit air.
define('SAI_ACTION_SET_SWIM', 61); //
define('SAI_ACTION_TELEPORT', 62); // Continue this action with the TARGET_TYPE column. Use any target_type (except 0), and use target_x, target_y, target_z, target_o as the coordinates
define('SAI_ACTION_SET_COUNTER', 63); //
define('SAI_ACTION_STORE_TARGET_LIST', 64); //
define('SAI_ACTION_WP_RESUME', 65); // Creature continues in its Waypoint Movement.
define('SAI_ACTION_SET_ORIENTATION', 66); //
define('SAI_ACTION_CREATE_TIMED_EVENT', 67); //
define('SAI_ACTION_PLAYMOVIE', 68); //
define('SAI_ACTION_MOVE_TO_POS', 69); // PointId is called by SMART_EVENT_MOVEMENTINFORM. Continue this action with the TARGET_TYPE column. Use any target_type, and use target_x, target_y, target_z, target_o as the coordinates
define('SAI_ACTION_ENABLE_TEMP_GOBJ', 70); // param1 = duration
define('SAI_ACTION_EQUIP', 71); // only slots with mask set will be sent to client, bits are 1, 2, 4, leaving mask 0 is defaulted to mask 7 (send all), Slots1-3 are only used if no Param1 is set
define('SAI_ACTION_CLOSE_GOSSIP', 72); // Closes gossip window.
define('SAI_ACTION_TRIGGER_TIMED_EVENT', 73); //
define('SAI_ACTION_REMOVE_TIMED_EVENT', 74); //
define('SAI_ACTION_ADD_AURA', 75); // Adds aura to player(s). Use target_type 17 to make AoE aura.
define('SAI_ACTION_OVERRIDE_SCRIPT_BASE_OBJECT', 76); // WARNING: CAN CRASH CORE, do not use if you dont know what you are doing
define('SAI_ACTION_RESET_SCRIPT_BASE_OBJECT', 77); //
define('SAI_ACTION_CALL_SCRIPT_RESET', 78); //
define('SAI_ACTION_SET_RANGED_MOVEMENT', 79); // Sets movement to follow at a specific range to the target.
define('SAI_ACTION_CALL_TIMED_ACTIONLIST', 80); //
define('SAI_ACTION_SET_NPC_FLAG', 81); //
define('SAI_ACTION_ADD_NPC_FLAG', 82); //
define('SAI_ACTION_REMOVE_NPC_FLAG', 83); //
define('SAI_ACTION_SIMPLE_TALK', 84); // Makes a player say text. SMART_EVENT_TEXT_OVER is not triggered and whispers can not be used.
define('SAI_ACTION_SELF_CAST', 85); // spellID, castFlags
define('SAI_ACTION_CROSS_CAST', 86); // This action is used to make selected caster (in CasterTargetType) to cast spell. Actual target is entered in target_type as normally.
define('SAI_ACTION_CALL_RANDOM_TIMED_ACTIONLIST', 87); // Will select one entry from the ones provided. 0 is ignored.
define('SAI_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST', 88); // 0 is ignored.
define('SAI_ACTION_RANDOM_MOVE', 89); // Creature moves to random position in given radius.
define('SAI_ACTION_SET_UNIT_FIELD_BYTES_1', 90); //
define('SAI_ACTION_REMOVE_UNIT_FIELD_BYTES_1', 91); //
define('SAI_ACTION_INTERRUPT_SPELL', 92); // This action allows you to interrupt the current spell being cast. If you do not set the spellId, the core will find the current spell depending on the withDelay and the withInstant values.
define('SAI_ACTION_SEND_GO_CUSTOM_ANIM', 93); //
define('SAI_ACTION_SET_DYNAMIC_FLAG', 94); //
define('SAI_ACTION_ADD_DYNAMIC_FLAG', 95); //
define('SAI_ACTION_REMOVE_DYNAMIC_FLAG', 96); //
define('SAI_ACTION_JUMP_TO_POS', 97); //
define('SAI_ACTION_SEND_GOSSIP_MENU', 98); // Can be used together with 'SMART_EVENT_GOSSIP_HELLO' to set custom gossip.
define('SAI_ACTION_GO_SET_LOOT_STATE', 99); //
define('SAI_ACTION_SEND_TARGET_TO_TARGET', 100); // Send targets previously stored with SMART_ACTION_STORE_TARGET, to another npc/go, the other npc/go can then access them as if it was its own stored list
define('SAI_ACTION_SET_HOME_POS', 101); // Use with SMART_TARGET_SELF or SMART_TARGET_POSITION
define('SAI_ACTION_SET_HEALTH_REGEN', 102); // Sets the current creatures health regen on or off.
define('SAI_ACTION_SET_ROOT', 103); // Enables or disables creature movement
define('SAI_ACTION_SET_GO_FLAG', 104); // oldFlag = newFlag
define('SAI_ACTION_ADD_GO_FLAG', 105); // oldFlag |= newFlag
define('SAI_ACTION_REMOVE_GO_FLAG', 106); // oldFlag &= ~newFlag
define('SAI_ACTION_SUMMON_CREATURE_GROUP', 107); // Use creature_summon_groups table. SAI target has no effect, use 0
define('SAI_ACTION_SET_POWER', 108); //
define('SAI_ACTION_ADD_POWER', 109); //
define('SAI_ACTION_REMOVE_POWER', 110); //
define('SAI_ACTION_GAME_EVENT_STOP', 111); //
define('SAI_ACTION_GAME_EVENT_START', 112); //
define('SAI_ACTION_START_CLOSEST_WAYPOINT', 113); // Make target follow closest waypoint to its location
define('SAI_ACTION_MOVE_OFFSET', 114); // Use target_x, target_y, target_z With target_type=1
define('SAI_ACTION_RANDOM_SOUND', 115); //
define('SAI_ACTION_SET_CORPSE_DELAY', 116); //
define('SAI_ACTION_DISABLE_EVADE', 117); //
define('SAI_ACTION_GO_SET_GO_STATE', 118); //
define('SAI_ACTION_SET_CAN_FLY', 119); //
define('SAI_ACTION_REMOVE_AURAS_BY_TYPE', 120); //
define('SAI_ACTION_SET_SIGHT_DIST', 121); //
define('SAI_ACTION_FLEE', 122); //
define('SAI_ACTION_ADD_THREAT', 123); //
define('SAI_ACTION_LOAD_EQUIPMENT', 124); //
define('SAI_ACTION_TRIGGER_RANDOM_TIMED_EVENT', 125); //
define('SAI_ACTION_REMOVE_ALL_GAMEOBJECTS', 126); //
define('SAI_ACTION_PAUSE_MOVEMENT', 127); // MovementSlot (default = 0, active = 1, controlled = 2), PauseTime (ms), Force
// define('SAI_ACTION_PLAY_ANIMKIT', 128); // don't use on 3.3.5a
// define('SAI_ACTION_SCENE_PLAY', 129); // don't use on 3.3.5a
// define('SAI_ACTION_SCENE_CANCEL', 130); // don't use on 3.3.5a
define('SAI_ACTION_SPAWN_SPAWNGROUP', 131); //
define('SAI_ACTION_DESPAWN_SPAWNGROUP', 132); //
define('SAI_ACTION_RESPAWN_BY_SPAWNID', 133); // type, typeGuid - Use to respawn npcs and gobs, the target in this case is always=1 and only a single unit could be a target via the spawnId (action_param1, action_param2)
define('SAI_ACTION_INVOKER_CAST', 134); // spellID, castFlags
define('SAI_ACTION_PLAY_CINEMATIC', 135); // cinematic
define('SAI_ACTION_SET_MOVEMENT_SPEED', 136); // movementType, speedInteger, speedFraction
define('SAI_ACTION_PLAY_SPELL_VISUAL_KIT', 137); // spellVisualKitId (RESERVED, PENDING CHERRYPICK)
define('SAI_ACTION_OVERRIDE_LIGHT', 138); // zoneId, areaLightId, overrideLightID, transitionMilliseconds
define('SAI_ACTION_OVERRIDE_WEATHER', 139); // zoneId, weatherId, intensity
define('SAI_ACTION_ALL_SPELLCASTS', [SAI_ACTION_CAST, SAI_ACTION_ADD_AURA, SAI_ACTION_INVOKER_CAST, SAI_ACTION_SELF_CAST, SAI_ACTION_CROSS_CAST]);
define('SAI_ACTION_ALL_TIMED_ACTION_LISTS', [SAI_ACTION_CALL_TIMED_ACTIONLIST, SAI_ACTION_CALL_RANDOM_TIMED_ACTIONLIST, SAI_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST]);
define('SAI_CAST_FLAG_INTERRUPT_PREV', 0x01);
define('SAI_CAST_FLAG_TRIGGERED', 0x02);
// define('SAI_CAST_FORCE_CAST', 0x04); // Forces cast even if creature is out of mana or out of range
// define('SAI_CAST_NO_MELEE_IF_OOM', 0x08); // Prevents creature from entering melee if out of mana or out of range
// define('SAI_CAST_FORCE_TARGET_SELF', 0x10); // the target to cast this spell on itself
define('SAI_CAST_FLAG_AURA_MISSING', 0x20);
define('SAI_CAST_FLAG_COMBAT_MOVE', 0x40);
define('SAI_REACT_PASSIVE', 0);
define('SAI_REACT_DEFENSIVE', 1);
define('SAI_REACT_AGGRESSIVE', 2);
define('SAI_REACT_ASSIST', 3);
define('SAI_SUMMON_TIMED_OR_DEAD_DESPAWN', 1);
define('SAI_SUMMON_TIMED_OR_CORPSE_DESPAWN', 2);
define('SAI_SUMMON_TIMED_DESPAWN', 3);
define('SAI_SUMMON_TIMED_DESPAWN_OOC', 4);
define('SAI_SUMMON_CORPSE_DESPAWN', 5);
define('SAI_SUMMON_CORPSE_TIMED_DESPAWN', 6);
define('SAI_SUMMON_DEAD_DESPAWN', 7);
define('SAI_SUMMON_MANUAL_DESPAWN', 8);
define('SAI_TARGET_NONE', 0); // None.
define('SAI_TARGET_SELF', 1); // Self cast.
define('SAI_TARGET_VICTIM', 2); // Our current target. (ie: highest aggro)
define('SAI_TARGET_HOSTILE_SECOND_AGGRO', 3); // Second highest aggro.
define('SAI_TARGET_HOSTILE_LAST_AGGRO', 4); // Dead last on aggro.
define('SAI_TARGET_HOSTILE_RANDOM', 5); // Just any random target on our threat list.
define('SAI_TARGET_HOSTILE_RANDOM_NOT_TOP', 6); // Any random target except top threat.
define('SAI_TARGET_ACTION_INVOKER', 7); // Unit who caused this Event to occur.
define('SAI_TARGET_POSITION', 8); // Use xyz from event params.
define('SAI_TARGET_CREATURE_RANGE', 9); // (Random?) creature with specified ID within specified range.
define('SAI_TARGET_CREATURE_GUID', 10); // Creature with specified GUID.
define('SAI_TARGET_CREATURE_DISTANCE', 11); // Creature with specified ID within distance. (Different from #9?)
define('SAI_TARGET_STORED', 12); // Uses pre-stored target(list)
define('SAI_TARGET_GAMEOBJECT_RANGE', 13); // (Random?) object with specified ID within specified range.
define('SAI_TARGET_GAMEOBJECT_GUID', 14); // Object with specified GUID.
define('SAI_TARGET_GAMEOBJECT_DISTANCE', 15); // Object with specified ID within distance. (Different from #13?)
define('SAI_TARGET_INVOKER_PARTY', 16); // Invoker's party members
define('SAI_TARGET_PLAYER_RANGE', 17); // (Random?) player within specified range.
define('SAI_TARGET_PLAYER_DISTANCE', 18); // (Random?) player within specified distance. (Different from #17?)
define('SAI_TARGET_CLOSEST_CREATURE', 19); // Closest creature with specified ID within specified range.
define('SAI_TARGET_CLOSEST_GAMEOBJECT', 20); // Closest object with specified ID within specified range.
define('SAI_TARGET_CLOSEST_PLAYER', 21); // Closest player within specified range.
define('SAI_TARGET_ACTION_INVOKER_VEHICLE', 22); // Unit's vehicle who caused this Event to occur
define('SAI_TARGET_OWNER_OR_SUMMONER', 23); // Unit's owner or summoner
define('SAI_TARGET_THREAT_LIST', 24); // All units on creature's threat list
define('SAI_TARGET_CLOSEST_ENEMY', 25); // Any attackable target (creature or player) within maxDist
define('SAI_TARGET_CLOSEST_FRIENDLY', 26); // Any friendly unit (creature, player or pet) within maxDist
define('SAI_TARGET_LOOT_RECIPIENTS', 27); // All tagging players
define('SAI_TARGET_FARTHEST', 28); // Farthest unit on the threat list
define('SAI_TARGET_VEHICLE_PASSENGER', 29); // Vehicle can target unit in given seat
define('SAI_TARGET_CLOSEST_UNSPAWNED_GO', 30); // entry(0any), maxDist
define('SAI_TEMPLATE_BASIC', 0); //
define('SAI_TEMPLATE_CASTER', 1); // +JOIN: target_param1 as castFlag
define('SAI_TEMPLATE_TURRET', 2); // +JOIN: target_param1 as castflag
define('SAI_TEMPLATE_PASSIVE', 3); //
define('SAI_TEMPLATE_CAGED_GO_PART', 4); //
define('SAI_TEMPLATE_CAGED_NPC_PART', 5); //
define('SAI_SPAWN_FLAG_NONE', 0x00);
define('SAI_SPAWN_FLAG_IGNORE_RESPAWN', 0x01); // onSpawnIn - ignore & reset respawn timer
define('SAI_SPAWN_FLAG_FORCE_SPAWN', 0x02); // onSpawnIn - force additional spawn if already in world
define('SAI_SPAWN_FLAG_NOSAVE_RESPAWN', 0x04); // onDespawn - remove respawn time
// profiler queue interactions
define('PR_QUEUE_STATUS_ENDED', 0);
define('PR_QUEUE_STATUS_WAITING', 1);
@@ -912,4 +1655,12 @@ define('PR_EXCLUDE_GROUP_WRONG_PROFESSION', PR_EXCLUDE_GROUP_REQ_FISHING | PR
define('PR_EXCLUDE_GROUP_REQ_CANT_BE_EXALTED', 0x400);
define('PR_EXCLUDE_GROUP_ANY', 0x7FF);
// Areatrigger types
define('AT_TYPE_NONE', 0);
define('AT_TYPE_TAVERN', 1);
define('AT_TYPE_TELEPORT', 2);
define('AT_TYPE_OBJECTIVE', 3);
define('AT_TYPE_SMART', 4);
define('AT_TYPE_SCRIPT', 5);
?>

View File

@@ -14,6 +14,25 @@ class Game
'9d9d9d', 'ffffff', '1eff00', '0070dd', 'a335ee', 'ff8000', 'e5cc80', 'e6cc80'
);
public static $specIconStrings = array(
-1 => 'inv_misc_questionmark',
0 => 'spell_nature_elementalabsorption',
6 => ['spell_deathknight_bloodpresence', 'spell_deathknight_frostpresence', 'spell_deathknight_unholypresence' ],
11 => ['spell_nature_starfall', 'ability_racial_bearform', 'spell_nature_healingtouch' ],
3 => ['ability_hunter_beasttaming', 'ability_marksmanship', 'ability_hunter_swiftstrike' ],
8 => ['spell_holy_magicalsentry', 'spell_fire_firebolt02', 'spell_frost_frostbolt02' ],
2 => ['spell_holy_holybolt', 'spell_holy_devotionaura', 'spell_holy_auraoflight' ],
5 => ['spell_holy_wordfortitude', 'spell_holy_holybolt', 'spell_shadow_shadowwordpain' ],
4 => ['ability_rogue_eviscerate', 'ability_backstab', 'ability_stealth' ],
7 => ['spell_nature_lightning', 'spell_nature_lightningshield', 'spell_nature_magicimmunity' ],
9 => ['spell_shadow_deathcoil', 'spell_shadow_metamorphosis', 'spell_shadow_rainoffire' ],
1 => ['ability_rogue_eviscerate', 'ability_warrior_innerrage', 'ability_warrior_defensivestance' ]
);
public static $classFileStrings = array(
null, 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid'
);
private static $combatRatingToItemMod = array( // zero-indexed idx:CR; val:Mod
null, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28,
@@ -25,19 +44,19 @@ class Game
ITEM_MOD_HEALTH_REGEN, ITEM_MOD_SPELL_PENETRATION, ITEM_MOD_BLOCK_VALUE
);
public static $questClasses = array( // taken from old aowow: 2 & 3 partially point to pointless mini-areas in front of dungeons
public static $questClasses = array(
-2 => [ 0],
0 => [ 1, 3, 4, 8, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 139, 267, 279, 1497, 1519, 1537, 2257, 3430, 3433, 3487, 4080, 4298],
1 => [ 14, 15, 16, 17, 141, 148, 215, 331, 357, 361, 400, 405, 406, 440, 490, 493, 618, 1216, 1377, 1637, 1638, 1657, 3524, 3525, 3557],
/*todo*/ 2 => [ 133, 206, 209, 491, 717, 718, 719, 722, 796, 978, 1196, 1337, 1417, 1581, 1583, 1584, 1941, 2017, 2057, 2100, 2366, 2367, 2437, 2557, 3477, 3562, 3713, 3714, 3715, 3716, 3717, 3789, 3790, 3791, 3792, 3845, 3846, 3847, 3849, 3905, 4095, 4100, 4120, 4196, 4228, 4264, 4272, 4375, 4415, 4494, 4723],
/*todo*/ 3 => [ 1977, 2159, 2562, 2677, 2717, 3428, 3429, 3456, 3606, 3805, 3836, 3840, 3842, 4273, 4500, 4722, 4812],
0 => [ 1, 3, 4, 8, 9, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 132, 139, 154, 267, 1497, 1519, 1537, 2257, 3430, 3431, 3433, 3487, 4080, 4298],
1 => [ 14, 15, 16, 17, 141, 148, 188, 215, 220, 331, 357, 361, 363, 400, 405, 406, 440, 490, 493, 618, 1377, 1637, 1638, 1657, 1769, 3524, 3525, 3526, 3557],
2 => [ 206, 209, 491, 717, 718, 719, 721, 722, 796, 1176, 1196, 1337, 1417, 1581, 1583, 1584, 1941, 2017, 2057, 2100, 2366, 2367, 2437, 2557, 3535, 3562, 3688, 3713, 3714, 3715, 3716, 3717, 3789, 3790, 3791, 3792, 3842, 3847, 3848, 3849, 3905, 4100, 4131, 4196, 4228, 4264, 4265, 4272, 4277, 4415, 4416, 4494, 4522, 4723, 4809, 4813, 4820],
3 => [ 1977, 2159, 2677, 2717, 3428, 3429, 3456, 3457, 3606, 3607, 3805, 3836, 3845, 3923, 3959, 4075, 4273, 4493, 4500, 4603, 4722, 4812, 4987],
4 => [ -372, -263, -262, -261, -162, -161, -141, -82, -81, -61],
5 => [ -373, -371, -324, -304, -264, -201, -182, -181, -121, -101, -24],
6 => [ -25, 2597, 3277, 3358, 3820, 4384, 4710],
7 => [-1010, -368, -367, -365, -344, -241, -1],
8 => [ 3483, 3518, 3519, 3520, 3521, 3522, 3523, 3679, 3703], // Skettis is no parent
9 => [-1006, -1005, -1003, -1002, -1001, -376, -375, -374, -370, -369, -366, -364, -284, -41, -22], // 22: seasonal, 284: special => not in the actual menu
10 => [ 65, 66, 67, 210, 394, 495, 3537, 3711, 4024, 4197, 4395, 4742] // Coldara is no parent
8 => [ 3483, 3518, 3519, 3520, 3521, 3522, 3523, 3679, 3703],
9 => [-1005, -1003, -1002, -1001, -376, -375, -374, -370, -369, -366, -364, -41, -22], // 22: seasonal
10 => [ 65, 66, 67, 210, 394, 495, 2817, 3537, 3711, 4024, 4197, 4395, 4742]
);
/* why:
@@ -61,38 +80,6 @@ class Game
)
);
public static $trainerTemplates = array( // TYPE => Id => templateList
TYPE_CLASS => array(
1 => [-200001, -200002], // Warrior
2 => [-200003, -200004, -200020, -200021], // Paladin
3 => [-200013, -200014], // Hunter
4 => [-200015, -200016], // Rogue
5 => [-200011, -200012], // Priest
6 => [-200019], // DK
7 => [-200017, -200018], // Shaman (HighlevelAlly Id missing..?)
8 => [-200007, -200008], // Mage
9 => [-200009, -200010], // Warlock
11 => [-200005, -200006] // Druid
),
TYPE_SKILL => array(
171 => [-201001, -201002, -201003], // Alchemy
164 => [-201004, -201005, -201006, -201007, -201008],// Blacksmithing
333 => [-201009, -201010, -201011], // Enchanting
202 => [-201012, -201013, -201014, -201015, -201016, -201017], // Engineering
182 => [-201018, -201019, -201020], // Herbalism
773 => [-201021, -201022, -201023], // Inscription
755 => [-201024, -201025, -201026], // Jewelcrafting
165 => [-201027, -201028, -201029, -201030, -201031, -201032], // Leatherworking
186 => [-201033, -201034, -201035], // Mining
393 => [-201036, -201037, -201038], // Skinning
197 => [-201039, -201040, -201041, -201042], // Tailoring
356 => [-202001, -202002, -202003], // Fishing
185 => [-202004, -202005, -202006], // Cooking
129 => [-202007, -202008, -202009], // First Aid
762 => [-202010, -202011, -202012] // Riding
)
);
public static $sockets = array( // jsStyle Strings
'meta', 'red', 'yellow', 'blue'
);
@@ -115,6 +102,13 @@ class Game
null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7
);
public static $areaFloors = array(
206 => 3, 209 => 7, 719 => 3, 721 => 4, 796 => 4, 1196 => 2, 1337 => 2, 1581 => 2, 1583 => 7, 1584 => 2,
2017 => 2, 2057 => 4, 2100 => 2, 2557 => 6, 2677 => 4, 3428 => 3, 3457 => 17, 3790 => 2, 3791 => 2, 3959 => 8,
3456 => 6, 3715 => 2, 3848 => 3, 3849 => 2, 4075 => 2, 4100 => 2, 4131 => 2, 4196 => 2, 4228 => 4, 4272 => 2,
4273 => 6, 4277 => 3, 4395 => 2, 4494 => 2, 4722 => 2, 4812 => 8
);
public static function itemModByRatingMask($mask)
{
if (($mask & 0x1C000) == 0x1C000) // special case resilience
@@ -210,9 +204,7 @@ class Game
);
// return list of integers, not strings
array_walk($data, function (&$v, $k) {
$v = intVal($v);
});
$data = array_map('intVal', $data);
return $data;
}
@@ -237,6 +229,262 @@ class Game
return $pages;
}
/*********************/
/* World Pos. Checks */
/*********************/
private static $alphaMapCache = [];
private static function alphaMapCheck(int $areaId, array &$set) : bool
{
$file = 'setup/generated/alphaMaps/'.$areaId.'.png';
if (!file_exists($file)) // file does not exist (probably instanced area)
return false;
// invalid and corner cases (literally)
if (!is_array($set) || empty($set['posX']) || empty($set['posY']) || $set['posX'] >= 100 || $set['posY'] >= 100)
{
$set = null;
return true;
}
if (empty(self::$alphaMapCache[$areaId]))
self::$alphaMapCache[$areaId] = imagecreatefrompng($file);
// alphaMaps are 1000 x 1000, adapt points [black => valid point]
if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10))
$set = null;
return true;
}
public static function checkCoords(array $points) : array
{
$result = [];
$capitals = array( // capitals take precedence over their surroundings
1497, 1637, 1638, 3487, // Undercity, Ogrimmar, Thunder Bluff, Silvermoon City
1519, 1537, 1657, 3557, // Stormwind City, Ironforge, Darnassus, The Exodar
3703, 4395 // Shattrath City, Dalaran
);
foreach ($points as $res)
{
if (self::alphaMapCheck($res['areaId'], $res))
{
if (!$res)
continue;
// some rough measure how central the spawn is on the map (the lower the number, the better)
// 0: perfect center; 1: touches a border
$q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) );
if (empty($result) || $result[0] > $q)
$result = [$q, $res];
}
else if (in_array($res['areaId'], $capitals)) // capitals (auto-discovered) and no hand-made alphaMap available
return $res;
else if (empty($result)) // add with lowest quality if alpha map is missing
$result = [1.0, $res];
}
// spawn does not really match on a map, but we need at least one result
if (!$result)
{
usort($points, function ($a, $b) { return ($a['dist'] < $b['dist']) ? -1 : 1; });
$result = [1.0, $points[0]];
}
return $result[1];
}
public static function getWorldPosForGUID(int $type, int ...$guids) : array
{
$result = [];
switch ($type)
{
case Type::NPC:
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids);
break;
case Type::OBJECT:
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids);
break;
case Type::SOUND:
$result = DB::AoWoW()->select('SELECT `soundId` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM dbc_soundemitters WHERE `soundId` IN (?a)', $guids);
break;
case Type::AREATRIGGER:
$result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM dbc_areatrigger WHERE `id` IN (?a)', $guids);
break;
default:
trigger_error('Game::getWorldPosForGUID - instanced with unsupported TYPE '.$type, E_USER_WARNING);
}
return $result;
}
public static function worldPosToZonePos(int $mapId, float $posX, float $posY, int $areaId = 0, int $floor = -1) : array
{
if (!$mapId < 0)
return [];
$query = 'SELECT
dm.id,
wma.areaId,
IFNULL(dm.floor, 0) AS floor,
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)), 1) AS `posX`,
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)), 1) AS `posY`,
SQRT(POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)) - 50), 2) +
POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)) - 50), 2)) AS `dist`
FROM
dbc_worldmaparea wma
LEFT JOIN
dbc_dungeonmap dm ON dm.mapId = IF(?d AND (wma.mapId NOT IN (0, 1, 530, 571) OR wma.areaId = 4395), wma.mapId, -1)
WHERE
wma.mapId = ?d AND IF(?d, wma.areaId = ?d, wma.areaId <> 0){ AND IF(dm.floor IS NULL, 1, dm.floor = ?d)}
HAVING
(`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9)
ORDER BY
`dist` ASC';
// dist BETWEEN 0 (center) AND 70.7 (corner)
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 1, $mapId, $areaId, $areaId, $floor < 0 ? DBSIMPLE_SKIP : $floor);
if (!$points) // retry: TC counts pre-instance subareas as instance-maps .. which have no map file
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 0, $mapId, 0, 0, DBSIMPLE_SKIP);
if (!is_array($points))
{
trigger_error('Game::worldPosToZonePos - dbc query failed', E_USER_ERROR);
return [];
}
return $points;
}
public static function getQuotesForCreature(int $creatureId, bool $asHTML = false, string $talkSource = '') : array
{
$nQuotes = 0;
$quotes = [];
$soundIds = [];
$quoteSrc = DB::World()->select('
SELECT
ct.GroupID AS ARRAY_KEY, ct.ID as ARRAY_KEY2,
ct.`Type` AS `talkType`,
ct.TextRange AS `range`,
IFNULL(bct.`LanguageID`, ct.`Language`) AS lang,
IFNULL(NULLIF(bct.Text, ""), IFNULL(NULLIF(bct.Text1, ""), IFNULL(ct.`Text`, ""))) AS text_loc0,
{IFNULL(NULLIF(bctl.Text, ""), IFNULL(NULLIF(bctl.Text1, ""), IFNULL(ctl.Text, ""))) AS text_loc?d,}
IF(bct.SoundEntriesID > 0, bct.SoundEntriesID, ct.Sound) AS soundId
FROM
creature_text ct
{LEFT JOIN
creature_text_locale ctl ON ct.CreatureID = ctl.CreatureID AND ct.GroupID = ctl.GroupID AND ct.ID = ctl.ID AND ctl.Locale = ?}
LEFT JOIN
broadcast_text bct ON ct.BroadcastTextId = bct.ID
{LEFT JOIN
broadcast_text_locale bctl ON ct.BroadcastTextId = bctl.ID AND bctl.locale = ?}
WHERE
ct.CreatureID = ?d',
User::$localeId ?: DBSIMPLE_SKIP,
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
$creatureId
);
foreach ($quoteSrc as $grp => $text)
{
$group = [];
foreach ($text as $t)
{
if ($t['soundId'])
$soundIds[] = $t['soundId'];
$msg = Util::localizedString($t, 'text');
if (!$msg)
continue;
// fixup .. either set %s for emotes or dont >.<
if (in_array($t['talkType'], [2, 16]) && strpos($msg, '%s') === false)
$msg = '%s '.$msg;
// fixup: bad case-insensivity
$msg = Util::parseHtmlText(str_replace('%S', '%s', htmlentities($msg)), !$asHTML);
if ($talkSource)
$msg = sprintf($msg, $talkSource);
// make type css compatible
switch ($t['talkType'])
{
case 1: // yell:
case 14: $t['talkType'] = 1; break; // - dark red
case 2: // emote:
case 16: // "
case 3: // boss emote:
case 41: $t['talkType'] = 4; break; // - orange
case 4: // whisper:
case 15: // "
case 5: // boss whisper:
case 42: $t['talkType'] = 3; break; // - pink-ish
default: $t['talkType'] = 2; // [type: 0, 12] say: yellow-ish
}
// prefix
$pre = '';
if ($t['talkType'] != 4)
$pre = ($talkSource ?: '%s').' '.Lang::npc('textTypes', $t['talkType']).Lang::main('colon').($t['lang'] ? '['.Lang::game('languages', $t['lang']).'] ' : null);
if ($asHTML)
$msg = '<div><span class="s'.$t['talkType'].'">%s'.($t['range'] ? sprintf(Util::$dfnString, Lang::npc('textRanges', $t['range']), $msg) : $msg).'</span></div>';
else
$msg = '[div][span class=s'.$t['talkType'].']%s'.html_entity_decode($msg).'[/span][/div]';
$line = array(
'range' => $t['range'],
'text' => $msg,
'prefix' => $pre
);
$nQuotes++;
$group[] = $line;
}
if ($group)
$quotes[$grp] = $group;
}
return [$quotes, $nQuotes, $soundIds];
}
public static function getBreakpointsForSkill(int $skillId, int $reqLevel) : array
{
switch ($skillId)
{
case SKILL_HERBALISM:
case SKILL_LOCKPICKING:
case SKILL_JEWELCRAFTING:
case SKILL_INSCRIPTION:
case SKILL_SKINNING:
case SKILL_MINING:
$points = [$reqLevel]; // red/orange
if ($reqLevel + 25 <= MAX_SKILL) // orange/yellow
$points[] = $reqLevel + 25;
if ($reqLevel + 50 <= MAX_SKILL) // yellow/green
$points[] = $reqLevel + 50;
if ($reqLevel + 100 <= MAX_SKILL) // green/grey
$points[] = $reqLevel + 100;
return $points;
default:
return [$reqLevel];
}
}
}
?>

View File

@@ -26,6 +26,7 @@ require_once 'includes/markup.class.php'; // manipulate markup
require_once 'includes/database.class.php'; // wrap DBSimple
require_once 'includes/community.class.php'; // handle comments, screenshots and videos
require_once 'includes/loot.class.php'; // build lv-tabs containing loot-information
require_once 'includes/smartAI.class.php';
require_once 'localization/lang.class.php';
require_once 'pages/genericPage.class.php';
@@ -92,10 +93,14 @@ if (!empty($AoWoWconf['characters']))
// load config to constants
function loadConfig(bool $noPHP = false) : void
{
$sets = DB::isConnectable(DB_AOWOW) ? DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value`, `flags` FROM ?_config') : [];
foreach ($sets as $k => $v)
{
$php = $v['flags'] & CON_FLAG_PHP;
if ($php && $noPHP)
continue;
// this should not have been possible
if (!strlen($v['value']) && !($v['flags'] & CON_FLAG_TYPE_STRING) && !$php)
@@ -125,10 +130,11 @@ foreach ($sets as $k => $v)
if ($php)
ini_set(strtolower($k), $val);
else
else if (!defined('CFG_'.strtoupper($k)))
define('CFG_'.strtoupper($k), $val);
}
}
loadConfig();
// handle non-fatal errors and notices
error_reporting(!empty($AoWoWconf['aowow']) && CFG_DEBUG ? E_AOWOW : 0);
@@ -156,10 +162,12 @@ set_error_handler(function($errNo, $errStr, $errFile, $errLine)
$errName = 'E_RECOVERABLE_ERROR';
Util::addNote($uGroup, $errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine);
if (CLI)
CLI::write($errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine, $errNo & 0x40A ? CLI::LOG_WARN : CLI::LOG_ERROR);
if (DB::isConnectable(DB_AOWOW))
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
AOWOW_REVISION, $errNo, $errFile, $errLine, CLI ? 'CLI' : $_SERVER['QUERY_STRING'], User::$groups, $errStr
AOWOW_REVISION, $errNo, $errFile, $errLine, CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $errStr
);
return true;
@@ -172,11 +180,11 @@ set_exception_handler(function ($ex)
if (DB::isConnectable(DB_AOWOW))
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
AOWOW_REVISION, $ex->getCode(), $ex->getFile(), $ex->getLine(), CLI ? 'CLI' : $_SERVER['QUERY_STRING'], User::$groups, $ex->getMessage()
AOWOW_REVISION, $ex->getCode(), $ex->getFile(), $ex->getLine(), CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $ex->getMessage()
);
if (!CLI)
(new GenericPage(null))->error();
(new GenericPage())->error();
else
echo 'Exception - '.$ex->getMessage()."\n ".$ex->getFile(). '('.$ex->getLine().")\n".$ex->getTraceAsString()."\n";
});
@@ -190,7 +198,7 @@ register_shutdown_function(function()
if (DB::isConnectable(DB_AOWOW))
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
AOWOW_REVISION, $e['type'], $e['file'], $e['line'], CLI ? 'CLI' : $_SERVER['QUERY_STRING'], User::$groups, $e['message']
AOWOW_REVISION, $e['type'], $e['file'], $e['line'], CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $e['message']
);
if (CLI)
@@ -220,25 +228,45 @@ if (!CLI)
session_set_cookie_params(15 * YEAR, '/', '', $secure, true);
session_cache_limiter('private');
session_start();
if (!session_start())
{
trigger_error('failed to start session', E_USER_ERROR);
exit;
}
if (!empty($AoWoWconf['aowow']) && User::init())
User::save(); // save user-variables in session
// set up some logging (~10 queries will execute before we init the user and load the config)
if (CFG_DEBUG && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
{
DB::Aowow()->setLogger(['DB', 'logger']);
DB::World()->setLogger(['DB', 'logger']);
if (DB::isConnected(DB_AUTH))
DB::Auth()->setLogger(['DB', 'logger']);
if (!empty($AoWoWconf['characters']))
foreach ($AoWoWconf['characters'] as $idx => $__)
if (DB::isConnected(DB_CHARACTERS . $idx))
DB::Characters($idx)->setLogger(['DB', 'logger']);
}
// hard-override locale for this call (should this be here..?)
// all strings attached..
if (!empty($AoWoWconf['aowow']))
{
if (isset($_GET['locale']) && (CFG_LOCALES & (1 << (int)$_GET['locale'])))
if (isset($_GET['locale']) && (int)$_GET['locale'] <= MAX_LOCALES && (int)$_GET['locale'] >= 0)
if (CFG_LOCALES & (1 << $_GET['locale']))
User::useLocale($_GET['locale']);
Lang::load(User::$localeString);
}
// parse page-parameters .. sanitize before use!
$str = explode('&', $_SERVER['QUERY_STRING'], 2)[0];
$str = explode('&', mb_strtolower($_SERVER['QUERY_STRING'] ?? ''), 2)[0];
$_ = explode('=', $str, 2);
$pageCall = $_[0];
$pageParam = isset($_[1]) ? $_[1] : null;
$pageParam = $_[1] ?? '';
Util::$wowheadLink = 'http://'.Util::$subDomains[User::$localeId].'.wowhead.com/'.$str;
}

View File

@@ -159,9 +159,8 @@ class DbSimple_Connect
*
* @param string $query запрос
*/
public function addInit($query)
public function addInit(...$args)
{
$args = func_get_args();
if ($this->DbSimple !== null)
return call_user_func_array(array(&$this->DbSimple, 'query'), $args);
$this->init[] = $args;

View File

@@ -144,9 +144,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* mixed select(string $query [, $arg1] [,$arg2] ...)
* Execute query and return the result.
*/
public function select($query)
public function select(...$args)
{
$args = func_get_args();
$total = false;
return $this->_query($args, $total);
}
@@ -157,10 +156,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* Total number of found rows (independent to LIMIT) is returned in $total
* (in most cases second query is performed to calculate $total).
*/
public function selectPage(&$total, $query)
public function selectPage(&$total, ...$args)
{
$args = func_get_args();
array_shift($args);
$total = true;
return $this->_query($args, $total);
}
@@ -173,9 +170,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* because PHP DOES NOT generates notice on $row['abc'] if $row === null
* or $row === false (but, if $row is empty array, notice is generated).
*/
public function selectRow()
public function selectRow(...$args)
{
$args = func_get_args();
$total = false;
$rows = $this->_query($args, $total);
if (!is_array($rows)) return $rows;
@@ -188,9 +184,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* array selectCol(string $query [, $arg1] [,$arg2] ...)
* Return the first column of query result as array.
*/
public function selectCol()
public function selectCol(...$args)
{
$args = func_get_args();
$total = false;
$rows = $this->_query($args, $total);
if (!is_array($rows)) return $rows;
@@ -203,9 +198,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* Return the first cell of the first column of query result.
* If no one row selected, return null.
*/
public function selectCell()
public function selectCell(...$args)
{
$args = func_get_args();
$total = false;
$rows = $this->_query($args, $total);
if (!is_array($rows)) return $rows;
@@ -221,9 +215,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* mixed query(string $query [, $arg1] [,$arg2] ...)
* Alias for select(). May be used for INSERT or UPDATE queries.
*/
public function query()
public function query(...$args)
{
$args = func_get_args();
$total = false;
return $this->_query($args, $total);
}
@@ -246,9 +239,8 @@ abstract class DbSimple_Database extends DbSimple_LastError
* Нужно для сложных запросов, состоящих из кусков, которые полезно сохранить
*
*/
public function subquery()
public function subquery(...$args)
{
$args = func_get_args();
$this->_expandPlaceholders($args,$this->_placeholderNativeArgs !== null);
return new DbSimple_SubQuery($args);
}

View File

@@ -159,6 +159,7 @@ class DbSimple_Mysqli extends DbSimple_Database
{
$this->_lastQuery = $queryMain;
$this->_expandPlaceholders($queryMain, false);
mysqli_ping($this->link);
$result = mysqli_query($this->link, $queryMain[0]);
if ($result === false)
return $this->_setDbError($queryMain[0]);

View File

@@ -0,0 +1,187 @@
<?php
/****************************************
Example of how to use this uploader class...
You can uncomment the following lines (minus the require) to use these as your defaults.
// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();
// max file size in bytes
$sizeLimit = 10 * 1024 * 1024;
require('valums-file-uploader/server/php.php');
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
// Call handleUpload() with the name of the folder, relative to PHP's getcwd()
$result = $uploader->handleUpload('uploads/');
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
/******************************************/
/**
* Handle file uploads via XMLHttpRequest
*/
class qqUploadedFileXhr
{
/**
* Save the file to the specified path
* @return boolean TRUE on success
*/
function save(string $path) : bool
{
$input = fopen("php://input", "r");
$temp = tmpfile();
$realSize = stream_copy_to_stream($input, $temp);
fclose($input);
if ($realSize != $this->getSize())
return false;
$target = fopen($path, "w");
fseek($temp, 0, SEEK_SET);
stream_copy_to_stream($temp, $target);
fclose($target);
return true;
}
function getName() : string
{
return $_GET['qqfile'];
}
function getSize(): int
{
if (isset($_SERVER["CONTENT_LENGTH"]))
return (int)$_SERVER["CONTENT_LENGTH"];
throw new Exception('Getting content length is not supported.');
return 0;
}
}
/**
* Handle file uploads via regular form post (uses the $_FILES array)
*/
class qqUploadedFileForm
{
/**
* Save the file to the specified path
* @return boolean TRUE on success
*/
function save(string $path) : bool
{
if(!move_uploaded_file($_FILES['qqfile']['tmp_name'], $path))
return false;
return true;
}
function getName() : string
{
return $_FILES['qqfile']['name'];
}
function getSize() : int
{
return $_FILES['qqfile']['size'];
}
}
class qqFileUploader
{
private $allowedExtensions = array();
private $sizeLimit = 10485760;
private $file;
public function __construct(array $allowedExtensions = array(), $sizeLimit = 10485760)
{
$this->allowedExtensions = array_map("strtolower", $allowedExtensions);
$this->sizeLimit = $sizeLimit;
$this->checkServerSettings();
if (isset($_GET['qqfile']))
$this->file = new qqUploadedFileXhr();
else if (isset($_FILES['qqfile']))
$this->file = new qqUploadedFileForm();
else
$this->file = null;
}
public function getName() : string
{
return $this->file?->getName() ?? '';
}
private function checkServerSettings() : void
{
$postSize = $this->toBytes(ini_get('post_max_size'));
$uploadSize = $this->toBytes(ini_get('upload_max_filesize'));
if ($postSize < $this->sizeLimit || $uploadSize < $this->sizeLimit)
{
$size = max(1, $this->sizeLimit / 1024 / 1024) . 'M';
die("{'error':'increase post_max_size and upload_max_filesize to $size'}");
}
}
private function toBytes(string $str) : int
{
$val = trim($str);
$last = strtolower(substr($str, -1, 1));
switch ($last)
{
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
}
return $val;
}
/**
* Returns array('success' => true, 'newFilename' => 'myDoc123.doc') or array('error' => 'error message')
*/
function handleUpload(string $uploadDirectory, string $newName = '', bool $replaceOldFile = FALSE) : array
{
if (!is_writable($uploadDirectory))
return ['error' => "Server error. Upload directory isn't writable."];
if (!$this->file)
return ['error' => 'No files were uploaded.'];
$size = $this->file->getSize();
if ($size == 0)
return ['error' => 'File is empty'];
if ($size > $this->sizeLimit)
return ['error' => 'File is too large'];
$pathinfo = pathinfo($this->getName());
$filename = $newName ?: $pathinfo['filename'];
//$filename = md5(uniqid());
$ext = @$pathinfo['extension']; // hide notices if extension is empty
if ($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions))
{
$these = implode(', ', $this->allowedExtensions);
return ['error' => 'File has an invalid extension, it should be one of '. $these . '.'];
}
// don't overwrite previous files that were uploaded
if (!$replaceOldFile)
while (file_exists($uploadDirectory . $filename . '.' . $ext))
$filename .= rand(10, 99);
if ($this->file->save($uploadDirectory . $filename . '.' . $ext))
return ['success' => true, 'newFilename' => $filename . '.' . $ext];
else
return ['error' => 'Could not save uploaded file. The upload was cancelled, or server error encountered'];
}
}

View File

@@ -25,6 +25,7 @@ class Loot
private $entry = 0; // depending on the lookup itemId oder templateId
private $results = [];
private $chanceMods = [];
private $lootTemplates = array(
LOOT_REFERENCE, // internal
LOOT_ITEM, // item
@@ -40,23 +41,23 @@ class Loot
LOOT_SPELL // spell
);
public function &iterate()
public function &iterate() : iterable
{
reset($this->results);
while (list($k, $__) = each($this->results))
foreach ($this->results as $k => $__)
yield $k => $this->results[$k];
}
public function getResult()
public function getResult() : array
{
return $this->results;
}
private function createStack($l) // issue: TC always has an equal distribution between min/max
private function createStack(array $l) : string // issue: TC always has an equal distribution between min/max
{
if (empty($l['min']) || empty($l['max']) || $l['max'] <= $l['min'])
return null;
return '';
$stack = [];
for ($i = $l['min']; $i <= $l['max']; $i++)
@@ -66,7 +67,7 @@ class Loot
return json_encode($stack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON !
}
private function storeJSGlobals($data)
private function storeJSGlobals(array $data) : void
{
foreach ($data as $type => $jsData)
{
@@ -81,7 +82,62 @@ class Loot
}
}
private function getByContainerRecursive($tableName, $lootId, &$handledRefs, $groupId = 0, $baseChance = 1.0)
private function calcChance(array $refs, array $parents = []) : array
{
$retData = [];
$retKeys = [];
foreach ($refs as $rId => $ref)
{
// check for possible database inconsistencies
if (!$ref['chance'] && !$ref['isGrouped'])
trigger_error('Loot by Item: Ungrouped Item/Ref '.$ref['item'].' has 0% chance assigned!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] > 100)
trigger_error('Loot by Item: Group with Item/Ref '.$ref['item'].' has '.number_format($ref['sumChance'], 2).'% total chance! Some items cannot drop!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] >= 100 && !$ref['chance'])
trigger_error('Loot by Item: Item/Ref '.$ref['item'].' with adaptive chance cannot drop. Group already at 100%!', E_USER_WARNING);
$chance = abs($ref['chance'] ?: (100 - $ref['sumChance']) / $ref['nZeroItems']) / 100;
// apply inherited chanceMods
if (isset($this->chanceMods[$ref['item']]))
{
$chance *= $this->chanceMods[$ref['item']][0];
$chance = 1 - pow(1 - $chance, $this->chanceMods[$ref['item']][1]);
}
// save chance for parent-ref
$this->chanceMods[$rId] = [$chance, $ref['multiplier']];
// refTemplate doesn't point to a new ref -> we are done
if (!in_array($rId, $parents))
{
$data = array(
'percent' => $chance,
'stack' => [$ref['min'], $ref['max']],
'count' => 1 // ..and one for the sort script
);
if ($_ = self::createStack($ref))
$data['pctstack'] = $_;
// sort highest chances first
$i = 0;
for (; $i < count($retData); $i++)
if ($retData[$i]['percent'] < $data['percent'])
break;
array_splice($retData, $i, 0, [$data]);
array_splice($retKeys, $i, 0, [$rId]);
}
}
return array_combine($retKeys, $retData);
}
private function getByContainerRecursive(string $tableName, int $lootId, array &$handledRefs, int $groupId = 0, float $baseChance = 1.0) : ?array
{
$loot = [];
$rawItems = [];
@@ -101,7 +157,8 @@ class Loot
'quest' => $entry['QuestRequired'],
'group' => $entry['GroupId'],
'parentRef' => $tableName == LOOT_REFERENCE ? $lootId : 0,
'realChanceMod' => $baseChance
'realChanceMod' => $baseChance,
'groupChance' => 0
);
// if ($entry['LootMode'] > 1)
@@ -134,7 +191,7 @@ class Loot
// bandaid.. remove when propperly handling lootmodes
if (!in_array($entry['Reference'], $handledRefs))
{ // todo (high): find out, why i used this in the first place. (don't do drugs, kids)
list($data, $raw) = self::getByContainerRecursive(LOOT_REFERENCE, $entry['Reference'], $handledRefs, /*$entry['GroupId'],*/ 0, $entry['Chance'] / 100);
[$data, $raw] = self::getByContainerRecursive(LOOT_REFERENCE, $entry['Reference'], $handledRefs, /*$entry['GroupId'],*/ 0, $entry['Chance'] / 100);
$handledRefs[] = $entry['Reference'];
@@ -166,12 +223,16 @@ class Loot
$set['groupChance'] = &$groupChances[$entry['GroupId']];
}
else if ($entry['GroupId'] && $entry['Chance'])
{
$set['groupChance'] = $entry['Chance'];
if (!$entry['Reference'])
{
if (empty($groupChances[$entry['GroupId']]))
$groupChances[$entry['GroupId']] = 0;
$groupChances[$entry['GroupId']] += $entry['Chance'];
$set['groupChance'] = $entry['Chance'];
}
}
else // shouldn't have happened
{
@@ -192,21 +253,19 @@ class Loot
trigger_error('Loot entry '.$lootId.' / group '.$k.' has a total chance of '.number_format($sum, 2).'%. Some items cannot drop!', E_USER_WARNING);
$sum = 100;
}
$cnt = empty($nGroupEquals[$k]) ? 1 : $nGroupEquals[$k];
$groupChances[$k] = (100 - $sum) / $cnt; // is applied as backReference to items with 0-chance
// is applied as backReference to items with 0-chance
$groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1);
}
return [$loot, array_unique($rawItems)];
}
public function getByContainer($table, $entry)
public function getByContainer(string $table, int $entry): bool
{
$this->entry = intVal($entry);
if (!in_array($table, $this->lootTemplates) || !$this->entry)
return null;
return false;
/*
todo (high): implement conditions on loot (and conditions in general)
@@ -269,7 +328,7 @@ class Loot
);
$this->results[] = array_merge($base, $data);
$this->jsGlobals[TYPE_ITEM][$loot['reference']] = $data;
$this->jsGlobals[Type::ITEM][$loot['reference']] = $data;
}
}
@@ -321,7 +380,7 @@ class Loot
return true;
}
public function getByItem($entry, $maxResults = CFG_SQL_LIMIT_DEFAULT, $lootTableList = [])
public function getByItem(int $entry, int $maxResults = CFG_SQL_LIMIT_DEFAULT, array $lootTableList = []) : bool
{
$this->entry = intVal($entry);
@@ -350,13 +409,12 @@ class Loot
['achievement', [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
);
$refResults = [];
$chanceMods = [];
$query = 'SELECT
lt1.entry AS ARRAY_KEY,
IF(lt1.reference = 0, lt1.item, lt1.reference) AS item,
lt1.chance,
SUM(IF(lt2.chance = 0, 1, 0)) AS nZeroItems,
SUM(lt2.chance) AS sumChance,
SUM(IF(lt2.reference = 0, lt2.chance, 0)) AS sumChance,
IF(lt1.groupid > 0, 1, 0) AS isGrouped,
IF(lt1.reference = 0, lt1.mincount, 1) AS min,
IF(lt1.reference = 0, lt1.maxcount, 1) AS max,
@@ -369,61 +427,6 @@ class Loot
%s
GROUP BY lt2.entry, lt2.groupid';
$calcChance = function ($refs, $parents = []) use (&$chanceMods)
{
$retData = [];
$retKeys = [];
foreach ($refs as $rId => $ref)
{
// check for possible database inconsistencies
if (!$ref['chance'] && !$ref['isGrouped'])
trigger_error('Loot by Item: Ungrouped Item/Ref '.$ref['item'].' has 0% chance assigned!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] > 100)
trigger_error('Loot by Item: Group with Item/Ref '.$ref['item'].' has '.number_format($ref['sumChance'], 2).'% total chance! Some items cannot drop!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] >= 100 && !$ref['chance'])
trigger_error('Loot by Item: Item/Ref '.$ref['item'].' with adaptive chance cannot drop. Group already at 100%!', E_USER_WARNING);
$chance = abs($ref['chance'] ?: (100 - $ref['sumChance']) / $ref['nZeroItems']) / 100;
// apply inherited chanceMods
if (isset($chanceMods[$ref['item']]))
{
$chance *= $chanceMods[$ref['item']][0];
$chance = 1 - pow(1 - $chance, $chanceMods[$ref['item']][1]);
}
// save chance for parent-ref
$chanceMods[$rId] = [$chance, $ref['multiplier']];
// refTemplate doesn't point to a new ref -> we are done
if (!in_array($rId, $parents))
{
$data = array(
'percent' => $chance,
'stack' => [$ref['min'], $ref['max']],
'count' => 1 // ..and one for the sort script
);
if ($_ = self::createStack($ref))
$data['pctstack'] = $_;
// sort highest chances first
$i = 0;
for (; $i < count($retData); $i++)
if ($retData[$i]['percent'] < $data['percent'])
break;
array_splice($retData, $i, 0, [$data]);
array_splice($retKeys, $i, 0, [$rId]);
}
}
return array_combine($retKeys, $retData);
};
/*
get references containing the item
*/
@@ -442,7 +445,7 @@ class Loot
array_keys($curRefs)
);
$refResults += $calcChance($curRefs, array_column($newRefs, 'item'));
$refResults += $this->calcChance($curRefs, array_column($newRefs, 'item'));
}
/*
@@ -453,7 +456,7 @@ class Loot
if ($lootTableList && !in_array($this->lootTemplates[$i], $lootTableList))
continue;
$result = $calcChance(DB::World()->select(
$result = $this->calcChance(DB::World()->select(
sprintf($query, '{lt1.reference IN (?a) OR }(lt1.reference = 0 AND lt1.item = ?d)'),
$this->lootTemplates[$i], $this->lootTemplates[$i],
$refResults ? array_keys($refResults) : DBSIMPLE_SKIP,
@@ -494,7 +497,7 @@ class Loot
$srcData = $srcObj->getListviewData();
foreach ($srcObj->iterate() as $__id => $curTpl)
foreach ($srcObj->iterate() as $curTpl)
{
switch ($curTpl['typeCat'])
{
@@ -628,7 +631,6 @@ class Loot
$this->results[$tabId] = [$data[0], $tabData];
}
return true;
}
}

View File

@@ -31,7 +31,8 @@ class Markup
$match[1] = 'achievement';
else if ($match[1] == 'icondb')
$match[1] = 'icon';
else if ($match[1] == 'money')
if ($match[1] == 'money')
{
if (stripos($match[0], 'items'))
{
@@ -39,7 +40,7 @@ class Markup
{
$sm = explode(',', $submatch[1]);
for ($i = 0; $i < count($sm); $i+=2)
$this->jsGlobals[TYPE_ITEM][$sm[$i]] = $sm[$i];
$this->jsGlobals[Type::ITEM][$sm[$i]] = $sm[$i];
}
}
@@ -49,11 +50,11 @@ class Markup
{
$sm = explode(',', $submatch[1]);
for ($i = 0; $i < count($sm); $i+=2)
$this->jsGlobals[TYPE_CURRENCY][$sm[$i]] = $sm[$i];
$this->jsGlobals[Type::CURRENCY][$sm[$i]] = $sm[$i];
}
}
}
else if ($type = array_search($match[1], Util::$typeStrings))
else if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
$this->jsGlobals[$type][$match[2]] = $match[2];
}
}
@@ -81,8 +82,8 @@ class Markup
$sm = explode(',', $submatch[1]);
for ($i = 0; $i < count($sm); $i += 2)
{
if (!empty($globals[TYPE_ITEM][1][$sm[$i]]))
$moneys[] = $globals[TYPE_ITEM][1][$sm[$i]]['name'];
if (!empty($globals[Type::ITEM][1][$sm[$i]]))
$moneys[] = $globals[Type::ITEM][1][$sm[$i]]['name'];
else
$moneys[] = Util::ucFirst(Lang::game('item')).' #'.$sm[$i];
}
@@ -96,8 +97,8 @@ class Markup
$sm = explode(',', $submatch[1]);
for ($i = 0; $i < count($sm); $i += 2)
{
if (!empty($globals[TYPE_CURRENCY][1][$sm[$i]]))
$moneys[] = $globals[TYPE_CURRENCY][1][$sm[$i]]['name'];
if (!empty($globals[Type::CURRENCY][1][$sm[$i]]))
$moneys[] = $globals[Type::CURRENCY][1][$sm[$i]]['name'];
else
$moneys[] = Util::ucFirst(Lang::game('curency')).' #'.$sm[$i];
}
@@ -106,8 +107,7 @@ class Markup
return Lang::concat($moneys);
}
if ($type = array_search($match[1], Util::$typeStrings))
if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
{
if (!empty($globals[$type][1][$match[2]]))
return $globals[$type][1][$match[2]]['name'];

View File

@@ -33,7 +33,11 @@ class Profiler
19 => [INVTYPE_TABARD], // tabard
);
public static $raidProgression = array( // statisticAchievement => relevantCriterium
public static $raidProgression = array( // statisticAchievement => relevantCriterium ; don't forget to enable this in /js/Profiler.js as well
1361 => 5100, 1362 => 5101, 1363 => 5102, 1365 => 5104, 1366 => 5108, 1364 => 5110, 1369 => 5112, 1370 => 5113, 1371 => 5114, 1372 => 5117, 1373 => 5119, 1374 => 5120, 1375 => 7805, 1376 => 5122, 1377 => 5123, // Naxxramas 10
1367 => 5103, 1368 => 5111, 1378 => 5124, 1379 => 5125, 1380 => 5126, 1381 => 5127, 1382 => 5128, 1383 => 7806, 1384 => 5130, 1385 => 5131, 1386 => 5132, 1387 => 5133, 1388 => 5134, 1389 => 5135, 1390 => 5136, // Naxxramas 25
2856 => 9938, 2857 => 9939, 2858 => 9940, 2859 => 9941, 2861 => 9943, 2865 => 9947, 2866 => 9948, 2868 => 9950, 2869 => 9951, 2870 => 9952, 2863 => 10558, 2864 => 10559, 2862 => 10560, 2867 => 10565, 2860 => 10580, // Ulduar 10
2872 => 9954, 2873 => 9955, 2874 => 9956, 2884 => 9957, 2875 => 9959, 2879 => 9963, 2880 => 9964, 2882 => 9966, 2883 => 9967, 3236 => 10542, 3257 => 10561, 3256 => 10562, 3258 => 10563, 2881 => 10566, 2885 => 10581, // Ulduar 25
1098 => 3271, // Onyxia's Lair 10
1756 => 13345, // Onyxia's Lair 25
4031 => 12230, 4034 => 12234, 4038 => 12238, 4042 => 12242, 4046 => 12246, // Trial of the Crusader 25 nh
@@ -44,10 +48,10 @@ class Profiler
4641 => 13092, 4655 => 13105, 4660 => 13109, 4663 => 13112, 4666 => 13115, 4669 => 13118, 4672 => 13121, 4675 => 13124, 4678 => 13127, 4681 => 13130, 4683 => 13133, 4687 => 13136, // Icecrown Citadel 25 nh
4640 => 13090, 4654 => 13104, 4659 => 13110, 4662 => 13113, 4665 => 13116, 4668 => 13119, 4671 => 13122, 4674 => 13125, 4677 => 13128, 4680 => 13131, 4684 => 13134, 4686 => 13137, // Icecrown Citadel 10 hc
4639 => 13089, 4643 => 13093, 4644 => 13094, 4645 => 13095, 4646 => 13096, 4647 => 13097, 4648 => 13098, 4649 => 13099, 4650 => 13100, 4651 => 13101, 4652 => 13102, 4653 => 13103, // Icecrown Citadel 10 nh
// 4823 => 13467, // Ruby Sanctum 25 hc
// 4820 => 13465, // Ruby Sanctum 25 nh
// 4822 => 13468, // Ruby Sanctum 10 hc
// 4821 => 13466, // Ruby Sanctum 10 nh
4823 => 13467, // Ruby Sanctum 25 hc
4820 => 13465, // Ruby Sanctum 25 nh
4822 => 13468, // Ruby Sanctum 10 hc
4821 => 13466, // Ruby Sanctum 10 nh
);
public static function getBuyoutForItem($itemId)
@@ -108,7 +112,6 @@ class Profiler
if ($queuePID && $queuePID != $pid)
{
trigger_error('pSync - another queue with PID #'.$queuePID.' is already running', E_USER_ERROR);
CLI::write('Profiler::queueLock() - another queue with PID #'.$queuePID.' is already runnung', CLI::LOG_ERROR);
return false;
}
@@ -179,7 +182,24 @@ class Profiler
{
if (DB::isConnectable(DB_AUTH) && !self::$realms)
{
self::$realms = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0 AND gamebuild = ?d', WOW_BUILD);
self::$realms = DB::Auth()->select('SELECT
id AS ARRAY_KEY,
`name`,
CASE
WHEN timezone IN (2, 3, 4) THEN "us"
WHEN timezone IN (8, 9, 10, 11, 12) THEN "eu"
WHEN timezone = 6 THEN "kr"
WHEN timezone = 14 THEN "tw"
WHEN timezone = 16 THEN "cn"
END AS region
FROM
realmlist
WHERE
allowedSecurityLevel = 0 AND
gamebuild = ?d',
WOW_BUILD
);
foreach (self::$realms as $rId => $rData)
{
if (DB::isConnectable(DB_CHARACTERS . $rId))
@@ -214,19 +234,19 @@ class Profiler
switch ($type)
{
case TYPE_PROFILE:
case Type::PROFILE:
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
self::queueInsert($realmId, $guid, TYPE_PROFILE, $newId);
self::queueInsert($realmId, $guid, Type::PROFILE, $newId);
break;
case TYPE_GUILD:
case Type::GUILD:
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
self::queueInsert($realmId, $guid, TYPE_GUILD, $newId);
self::queueInsert($realmId, $guid, Type::GUILD, $newId);
break;
case TYPE_ARENA_TEAM:
case Type::ARENA_TEAM:
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
self::queueInsert($realmId, $guid, TYPE_ARENA_TEAM, $newId);
self::queueInsert($realmId, $guid, Type::ARENA_TEAM, $newId);
break;
default:
@@ -244,7 +264,7 @@ class Profiler
public static function resyncStatus($type, array $subjectGUIDs)
{
$response = [CFG_PROFILER_QUEUE ? 2 : 0]; // in theory you could have multiple queues; used as divisor for: (15 / x) + 2
$response = [CFG_PROFILER_ENABLE ? 2 : 0]; // in theory you could have multiple queues; used as divisor for: (15 / x) + 2
if (!$subjectGUIDs)
$response[] = [PR_QUEUE_STATUS_ENDED, 0, 0, PR_QUEUE_ERROR_CHAR];
else
@@ -252,14 +272,14 @@ class Profiler
// error out all profiles with status WORKING, that are older than 60sec
DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d, errorCode = ?d WHERE status = ?d AND requestTime < ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_UNK, PR_QUEUE_STATUS_WORKING, time() - MINUTE);
$subjectStatus = DB::Aowow()->select('SELECT typeId AS ARRAY_KEY, status, realm FROM ?_profiler_sync WHERE `type` = ?d AND typeId IN (?a)', $type, $subjectGUIDs);
$subjectStatus = DB::Aowow()->select('SELECT typeId AS ARRAY_KEY, status, realm, errorCode FROM ?_profiler_sync WHERE `type` = ?d AND typeId IN (?a)', $type, $subjectGUIDs);
$queue = DB::Aowow()->selectCol('SELECT CONCAT(type, ":", typeId) FROM ?_profiler_sync WHERE status = ?d AND requestTime < UNIX_TIMESTAMP() ORDER BY requestTime ASC', PR_QUEUE_STATUS_WAITING);
foreach ($subjectGUIDs as $guid)
{
if (empty($subjectStatus[$guid])) // whelp, thats some error..
$response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_UNK];
else if ($subjectStatus[$guid]['status'] == PR_QUEUE_STATUS_ERROR)
$response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, $subjectStatus[$guid]['errCode']];
$response[] = [PR_QUEUE_STATUS_ERROR, 0, 0, $subjectStatus[$guid]['errorCode']];
else
$response[] = array(
$subjectStatus[$guid]['status'],
@@ -281,11 +301,25 @@ class Profiler
return false;
// reminder: this query should not fail: a placeholder entry is created as soon as a char listview is created or profile detail page is called
$profileId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $char['guid']);
$profile = DB::Aowow()->selectRow('SELECT id, lastupdated FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $char['guid']);
if (!$profile)
return false; // well ... it failed
$profileId = $profile['id'];
CLI::write('fetching char '.$char['name'].' (#'.$charGuid.') from realm #'.$realmId);
if (!$char['online'] && $char['logout_time'] <= $profile['lastupdated'])
{
DB::Aowow()->query('UPDATE ?_profiler_profiles SET lastupdated = ?d WHERE id = ?d', time(), $profileId);
CLI::write('char did not log in since last update. skipping...');
return true;
}
CLI::write('fetching char #'.$charGuid.' from realm #'.$realmId);
CLI::write('writing...');
$ra = (1 << ($char['race'] - 1));
$cl = (1 << ($char['class'] - 1));
/*************/
/* equipment */
@@ -367,15 +401,16 @@ class Profiler
'realm' => $realmId,
'realmGUID' => $charGuid,
'name' => $char['name'],
'renameItr' => 0,
'race' => $char['race'],
'class' => $char['class'],
'level' => $char['level'],
'gender' => $char['gender'],
'skincolor' => $char['playerBytes'] & 0xFF,
'facetype' => ($char['playerBytes'] >> 8) & 0xFF, // maybe features
'hairstyle' => ($char['playerBytes'] >> 16) & 0xFF,
'haircolor' => ($char['playerBytes'] >> 24) & 0xFF,
'features' => $char['playerBytes2'] & 0xFF, // maybe facetype
'skincolor' => $char['skin'],
'facetype' => $char['face'], // maybe features
'hairstyle' => $char['hairStyle'],
'haircolor' => $char['hairColor'],
'features' => $char['facialStyle'], // maybe facetype
'title' => $char['chosenTitle'] ? DB::Aowow()->selectCell('SELECT id FROM ?_titles WHERE bitIdx = ?d', $char['chosenTitle']) : 0,
'playedtime' => $char['totaltime'],
'nomodelMask' => ($char['playerFlags'] & 0x400 ? (1 << SLOT_HEAD) : 0) | ($char['playerFlags'] & 0x800 ? (1 << SLOT_BACK) : 0),
@@ -386,28 +421,34 @@ class Profiler
'talentbuild2' => '',
'glyphs1' => '',
'glyphs2' => '',
'activespec' => $char['activespec'],
'activespec' => $char['activeTalentGroup'],
'guild' => null,
'guildRank' => null,
'gearscore' => 0,
'achievementpoints' => 0
);
// char is flagged for rename
if ($char['at_login'] & 0x1)
{
$ri = DB::Aowow()->selectCell('SELECT MAX(renameItr) FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d AND name = ?', $realmId, $charGuid, $char['name']);
$data['renameItr'] = $ri ? ++$ri : 1;
}
/********************/
/* talents + glyphs */
/********************/
$t = DB::Characters($realmId)->selectCol('SELECT spec AS ARRAY_KEY, spell AS ARRAY_KEY2, spell FROM character_talent WHERE guid = ?d', $char['guid']);
$g = DB::Characters($realmId)->select('SELECT spec AS ARRAY_KEY, glyph1 AS g1, glyph2 AS g4, glyph3 AS g5, glyph4 AS g2, glyph5 AS g3, glyph6 AS g6 FROM character_glyphs WHERE guid = ?d', $char['guid']);
$t = DB::Characters($realmId)->selectCol('SELECT talentGroup AS ARRAY_KEY, spell AS ARRAY_KEY2, spell FROM character_talent WHERE guid = ?d', $char['guid']);
$g = DB::Characters($realmId)->select('SELECT talentGroup AS ARRAY_KEY, glyph1 AS g1, glyph2 AS g4, glyph3 AS g5, glyph4 AS g2, glyph5 AS g3, glyph6 AS g6 FROM character_glyphs WHERE guid = ?d', $char['guid']);
for ($i = 0; $i < 2; $i++)
{
// talents
for ($j = 0; $j < 3; $j++)
{
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell in (?a), rank, 0)) FROM ?_talents WHERE class = ?d AND tab = ?d GROUP BY id ORDER BY row, col ASC', !empty($t[$i]) ? $t[$i] : [0], $char['class'], $j);
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell IN (?a), `rank`, 0)) FROM ?_talents WHERE class = ?d AND tab = ?d GROUP BY id ORDER BY `row`, `col` ASC', !empty($t[$i]) ? $t[$i] : [0], $char['class'], $j);
$data['talentbuild'.($i + 1)] .= implode('', $_);
if ($char['activespec'] == $i)
if ($data['activespec'] == $i)
$data['talenttree'.($j + 1)] = array_sum($_);
}
@@ -443,7 +484,7 @@ class Profiler
if ($gemItems)
{
$gemScores = new ItemList(array(['id', array_column($gemItems, 0)]));
foreach ($gemItems as list($itemId, $mult))
foreach ($gemItems as [$itemId, $mult])
if (isset($gemScores->json[$itemId]['gearscore']))
$data['gearscore'] += $gemScores->json[$itemId]['gearscore'] * $mult;
}
@@ -472,7 +513,7 @@ class Profiler
/* hunter pets */
/***************/
if ((1 << ($char['class'] - 1)) == CLASS_HUNTER)
if ($cl == CLASS_HUNTER)
{
DB::Aowow()->query('DELETE FROM ?_profiler_pets WHERE owner = ?d', $profileId);
$pets = DB::Characters($realmId)->select('SELECT id AS ARRAY_KEY, id, entry, modelId, name FROM character_pet WHERE owner = ?d', $charGuid);
@@ -481,7 +522,7 @@ class Profiler
$morePet = DB::Aowow()->selectRow('SELECT p.`type`, c.family FROM ?_pet p JOIN ?_creature c ON c.family = p.id WHERE c.id = ?d', $petData['entry']);
$petSpells = DB::Characters($realmId)->selectCol('SELECT spell FROM pet_spell WHERE guid = ?d', $petGuid);
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell in (?a), rank, 0)) FROM ?_talents WHERE class = 0 AND petTypeMask = ?d GROUP BY id ORDER BY row, col ASC', $petSpells ?: [0], 1 << $morePet['type']);
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell IN (?a), `rank`, 0)) FROM ?_talents WHERE class = 0 AND petTypeMask = ?d GROUP BY id ORDER BY row, col ASC', $petSpells ?: [0], 1 << $morePet['type']);
$pet = array(
'id' => $petGuid,
'owner' => $profileId,
@@ -506,7 +547,7 @@ class Profiler
DB::Aowow()->query('DELETE FROM ?_profiler_completion WHERE id = ?d', $profileId);
// done quests
if ($quests = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, quest AS typeId FROM character_queststatus_rewarded WHERE guid = ?d', $profileId, TYPE_QUEST, $char['guid']))
if ($quests = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, quest AS typeId FROM character_queststatus_rewarded WHERE guid = ?d', $profileId, Type::QUEST, $char['guid']))
foreach (Util::createSqlBatchInsert($quests) as $q)
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$q, array_keys($quests[0]));
@@ -515,7 +556,7 @@ class Profiler
// known skills (professions only)
$skAllowed = DB::Aowow()->selectCol('SELECT id FROM ?_skillline WHERE typeCat IN (9, 11) AND (cuFlags & ?d) = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW);
$skills = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, skill AS typeId, `value` AS cur, max FROM character_skills WHERE guid = ?d AND skill IN (?a)', $profileId, TYPE_SKILL, $char['guid'], $skAllowed);
$skills = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, skill AS typeId, `value` AS cur, max FROM character_skills WHERE guid = ?d AND skill IN (?a)', $profileId, Type::SKILL, $char['guid'], $skAllowed);
// manually apply racial profession bonuses
foreach ($skills as &$sk)
@@ -544,14 +585,66 @@ class Profiler
unset($sk);
if ($skills)
{
// apply auto-learned trade skills
DB::Aowow()->query('
INSERT INTO ?_profiler_completion
SELECT ?d, ?d, spellId, NULL, NULL
FROM dbc_skilllineability
WHERE skillLineId IN (?a) AND
acquireMethod = 1 AND
(reqRaceMask = 0 OR reqRaceMask & ?d) AND
(reqClassMask = 0 OR reqClassMask & ?d)',
$profileId, Type::SPELL,
array_column($skills, 'typeId'),
1 << ($char['race'] - 1),
1 << ($char['class'] - 1)
);
foreach (Util::createSqlBatchInsert($skills) as $sk)
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$sk, array_keys($skills[0]));
}
CLI::write(' ..professions');
// reputation
if ($reputation = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, faction AS typeId, standing AS cur FROM character_reputation WHERE guid = ?d AND (flags & 0xC) = 0', $profileId, TYPE_FACTION, $char['guid']))
// get base values for this race/class
$reputation = [];
$baseRep = DB::Aowow()->selectCol('
SELECT id AS ARRAY_KEY, baseRepValue1 FROM aowow_factions WHERE baseRepValue1 && (baseRepRaceMask1 & ?d || (!baseRepRaceMask1 AND baseRepClassMask1)) &&
((baseRepClassMask1 & ?d) || !baseRepClassMask1) UNION
SELECT id AS ARRAY_KEY, baseRepValue2 FROM aowow_factions WHERE baseRepValue2 && (baseRepRaceMask2 & ?d || (!baseRepRaceMask2 AND baseRepClassMask2)) &&
((baseRepClassMask2 & ?d) || !baseRepClassMask2) UNION
SELECT id AS ARRAY_KEY, baseRepValue3 FROM aowow_factions WHERE baseRepValue3 && (baseRepRaceMask3 & ?d || (!baseRepRaceMask3 AND baseRepClassMask3)) &&
((baseRepClassMask3 & ?d) || !baseRepClassMask3) UNION
SELECT id AS ARRAY_KEY, baseRepValue4 FROM aowow_factions WHERE baseRepValue4 && (baseRepRaceMask4 & ?d || (!baseRepRaceMask4 AND baseRepClassMask4)) &&
((baseRepClassMask4 & ?d) || !baseRepClassMask4)
', $ra, $cl, $ra, $cl, $ra, $cl, $ra, $cl);
if ($reputation = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, faction AS typeId, standing AS cur FROM character_reputation WHERE guid = ?d AND (flags & 0x4) = 0', $profileId, Type::FACTION, $char['guid']))
{
// merge back base values for encountered factions
foreach ($reputation as &$set)
{
if (empty($baseRep[$set['typeId']]))
continue;
$set['cur'] += $baseRep[$set['typeId']];
unset($baseRep[$set['typeId']]);
}
}
// insert base values for not yet encountered factions
foreach ($baseRep as $id => $val)
$reputation[] = array(
'id' => $profileId,
'type' => Type::FACTION,
'typeId' => $id,
'cur' => $val
);
foreach (Util::createSqlBatchInsert($reputation) as $rep)
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$rep, array_keys($reputation[0]));
@@ -567,13 +660,13 @@ class Profiler
$indizes[] = $j + ($i * 32);
if ($indizes)
DB::Aowow()->query('INSERT INTO ?_profiler_completion SELECT ?d, ?d, id, NULL, NULL FROM ?_titles WHERE bitIdx IN (?a)', $profileId, TYPE_TITLE, $indizes);
DB::Aowow()->query('INSERT INTO ?_profiler_completion SELECT ?d, ?d, id, NULL, NULL FROM ?_titles WHERE bitIdx IN (?a)', $profileId, Type::TITLE, $indizes);
CLI::write(' ..titles');
// achievements
if ($achievements = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, achievement AS typeId, date AS cur FROM character_achievement WHERE guid = ?d', $profileId, TYPE_ACHIEVEMENT, $char['guid']))
if ($achievements = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, achievement AS typeId, date AS cur FROM character_achievement WHERE guid = ?d', $profileId, Type::ACHIEVEMENT, $char['guid']))
{
foreach (Util::createSqlBatchInsert($achievements) as $a)
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$a, array_keys($achievements[0]));
@@ -585,7 +678,7 @@ class Profiler
// raid progression
if ($progress = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, criteria AS typeId, date AS cur, counter AS `max` FROM character_achievement_progress WHERE guid = ?d AND criteria IN (?a)', $profileId, TYPE_ACHIEVEMENT, $char['guid'], self::$raidProgression))
if ($progress = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, criteria AS typeId, date AS cur, counter AS `max` FROM character_achievement_progress WHERE guid = ?d AND criteria IN (?a)', $profileId, Type::ACHIEVEMENT, $char['guid'], self::$raidProgression))
{
array_walk($progress, function (&$val) { $val['typeId'] = array_search($val['typeId'], self::$raidProgression); });
foreach (Util::createSqlBatchInsert($progress) as $p)
@@ -596,7 +689,7 @@ class Profiler
// known spells
if ($spells = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, spell AS typeId FROM character_spell WHERE guid = ?d AND disabled = 0', $profileId, TYPE_SPELL, $char['guid']))
if ($spells = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, spell AS typeId FROM character_spell WHERE guid = ?d AND disabled = 0', $profileId, Type::SPELL, $char['guid']))
foreach (Util::createSqlBatchInsert($spells) as $s)
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$s, array_keys($spells[0]));
@@ -698,7 +791,7 @@ class Profiler
// ranks
DB::Aowow()->query('DELETE FROM ?_profiler_guild_rank WHERE guildId = ?d', $guildId);
if ($ranks = DB::Characters($realmId)->select('SELECT ?d AS guildId, rid AS rank, rname AS name FROM guild_rank WHERE guildid = ?d', $guildId, $guildGuid))
if ($ranks = DB::Characters($realmId)->select('SELECT ?d AS guildId, rid AS `rank`, rname AS name FROM guild_rank WHERE guildid = ?d', $guildId, $guildGuid))
foreach (Util::createSqlBatchInsert($ranks) as $r)
DB::Aowow()->query('INSERT INTO ?_profiler_guild_rank (?#) VALUES '.$r, array_keys(reset($ranks)));
@@ -737,7 +830,7 @@ class Profiler
public static function getArenaTeamFromRealm($realmId, $teamGuid)
{
$team = DB::Characters($realmId)->selectRow('SELECT arenaTeamId, name, type, captainGuid, rating, seasonGames, seasonWins, weekGames, weekWins, rank, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM arena_team WHERE arenaTeamId = ?d', $teamGuid);
$team = DB::Characters($realmId)->selectRow('SELECT arenaTeamId, name, type, captainGuid, rating, seasonGames, seasonWins, weekGames, weekWins, `rank`, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM arena_team WHERE arenaTeamId = ?d', $teamGuid);
if (!$team)
return false;

View File

@@ -1,17 +1,17 @@
<?php
define('AOWOW_REVISION', 27);
define('AOWOW_REVISION', 33);
define('CLI', PHP_SAPI === 'cli');
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'];
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'/*, 'gmp'*/];
$error = '';
foreach ($reqExt as $r)
if (!extension_loaded($r))
$error .= 'Required Extension <b>'.$r."</b> was not found. Please check if it should exist, using \"<i>php -m</i>\"\n\n";
if (version_compare(PHP_VERSION, '5.5.0') < 0)
$error .= 'PHP Version <b>5.5.0</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
if (version_compare(PHP_VERSION, '7.4.0') < 0)
$error .= 'PHP Version <b>7.4</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
if ($error)
{

1622
includes/smartAI.class.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ class AchievementList extends BaseType
{
use listviewHelper;
public static $type = TYPE_ACHIEVEMENT;
public static $type = Type::ACHIEVEMENT;
public static $brickFile = 'achievement';
public static $dataTable = '?_achievement';
@@ -36,14 +36,16 @@ class AchievementList extends BaseType
$rewards = DB::World()->select('
SELECT
ar.ID AS ARRAY_KEY, ar.TitleA, ar.TitleH, ar.ItemID, ar.Sender AS sender, ar.MailTemplateID,
ar.Subject AS subject_loc0, IFNULL(arl2.Subject, "") AS subject_loc2, IFNULL(arl3.Subject, "") AS subject_loc3, IFNULL(arl6.Subject, "") AS subject_loc6, IFNULL(arl8.Subject, "") AS subject_loc8,
ar.Text AS text_loc0, IFNULL(arl2.Text, "") AS text_loc2, IFNULL(arl3.Text, "") AS text_loc3, IFNULL(arl6.Text, "") AS text_loc6, IFNULL(arl8.Text, "") AS text_loc8
ar.Subject AS subject_loc0, IFNULL(arl2.Subject, "") AS subject_loc2, IFNULL(arl3.Subject, "") AS subject_loc3, IFNULL(arl4.Subject, "") AS subject_loc4, IFNULL(arl6.Subject, "") AS subject_loc6, IFNULL(arl8.Subject, "") AS subject_loc8,
ar.Body AS text_loc0, IFNULL(arl2.Body, "") AS text_loc2, IFNULL(arl3.Body, "") AS text_loc3, IFNULL(arl4.Body, "") AS text_loc4, IFNULL(arl6.Body, "") AS text_loc6, IFNULL(arl8.Body, "") AS text_loc8
FROM
achievement_reward ar
LEFT JOIN
achievement_reward_locale arl2 ON arl2.ID = ar.ID AND arl2.Locale = "frFR"
LEFT JOIN
achievement_reward_locale arl3 ON arl3.ID = ar.ID AND arl3.Locale = "deDE"
LEFT JOIN
achievement_reward_locale arl4 ON arl4.ID = ar.ID AND arl4.Locale = "zhCN"
LEFT JOIN
achievement_reward_locale arl6 ON arl6.ID = ar.ID AND arl6.Locale = "esES"
LEFT JOIN
@@ -61,31 +63,33 @@ class AchievementList extends BaseType
{
$_curTpl = array_merge($rewards[$_id], $_curTpl);
$_curTpl['mailTemplate'] = $rewards[$_id]['MailTemplateID'];
if ($rewards[$_id]['MailTemplateID'])
{
// using class Loot creates an inifinite loop cirling between Loot, ItemList and SpellList or something
// $mailSrc = new Loot();
// $mailSrc->getByContainer(LOOT_MAIL, $rewards[$_id]['mailTemplate']);
// $mailSrc->getByContainer(LOOT_MAIL, $rewards[$_id]['MailTemplateID']);
// foreach ($mailSrc->iterate() as $loot)
// $_curTpl['rewards'][] = [TYPE_ITEM, $loot['id']];
// $_curTpl['rewards'][] = [Type::ITEM, $loot['id']];
// lets just assume for now, that mailRewards for achievements do not contain references
$mailRew = DB::World()->selectCol('SELECT Item FROM mail_loot_template WHERE Reference <= 0 AND entry = ?d', $rewards[$_id]['MailTemplateID']);
foreach ($mailRew AS $mr)
$_curTpl['rewards'][] = [TYPE_ITEM, $mr];
$_curTpl['rewards'][] = [Type::ITEM, $mr];
}
}
//"rewards":[[11,137],[3,138]] [type, typeId]
if (!empty($_curTpl['ItemID']))
$_curTpl['rewards'][] = [TYPE_ITEM, $_curTpl['ItemID']];
$_curTpl['rewards'][] = [Type::ITEM, $_curTpl['ItemID']];
if (!empty($_curTpl['itemExtra']))
$_curTpl['rewards'][] = [TYPE_ITEM, $_curTpl['itemExtra']];
$_curTpl['rewards'][] = [Type::ITEM, $_curTpl['itemExtra']];
if (!empty($_curTpl['TitleA']))
$_curTpl['rewards'][] = [TYPE_TITLE, $_curTpl['TitleA']];
$_curTpl['rewards'][] = [Type::TITLE, $_curTpl['TitleA']];
if (!empty($_curTpl['TitleH']))
if (empty($_curTpl['TitleA']) || $_curTpl['TitleA'] != $_curTpl['TitleH'])
$_curTpl['rewards'][] = [TYPE_TITLE, $_curTpl['TitleH']];
$_curTpl['rewards'][] = [Type::TITLE, $_curTpl['TitleH']];
// icon
$_curTpl['iconString'] = $_curTpl['iconString'] ?: 'trade_engineering';
@@ -99,7 +103,7 @@ class AchievementList extends BaseType
foreach ($this->iterate() as $__)
{
if ($addMask & GLOBALINFO_SELF)
$data[TYPE_ACHIEVEMENT][$this->id] = ['icon' => $this->curTpl['iconString'], 'name' => $this->getField('name', true)];
$data[Type::ACHIEVEMENT][$this->id] = ['icon' => $this->curTpl['iconString'], 'name' => $this->getField('name', true)];
if ($addMask & GLOBALINFO_REWARDS)
foreach ($this->curTpl['rewards'] as $_)
@@ -147,7 +151,7 @@ class AchievementList extends BaseType
if (isset($this->criteria[$this->id]))
return $this->criteria[$this->id];
$result = DB::Aowow()->Select('SELECT * FROM ?_achievementcriteria WHERE `refAchievementId` = ?d ORDER BY `order` ASC', $this->id);
$result = DB::Aowow()->Select('SELECT * FROM ?_achievementcriteria WHERE `refAchievementId` = ?d ORDER BY `order` ASC', $this->curTpl['refAchievement'] ?: $this->id);
if (!$result)
return [];
@@ -222,7 +226,7 @@ class AchievementList extends BaseType
break;
}
$criteria .= '<!--cr'.$crt['id'].':'.$crt['type'].':'.$crt['value1'].'-->- '.Util::jsEscape($crtName);
$criteria .= '<!--cr'.$crt['id'].':'.$crt['type'].':'.$crt['value1'].'-->- '.$crtName;
if ($crt['completionFlags'] & ACHIEVEMENT_CRITERIA_FLAG_MONEY_COUNTER)
$criteria .= '&nbsp;<span class="moneygold">'.Lang::nf($crt['value2' ] / 10000).'</span>';
@@ -234,13 +238,13 @@ class AchievementList extends BaseType
}
$x = '<table><tr><td><b class="q">';
$x .= Util::jsEscape($name);
$x .= $name;
$x .= '</b></td></tr></table>';
if ($description || $criteria)
$x .= '<table><tr><td>';
if ($description)
$x .= '<br />'.Util::jsEscape($description).'<br />';
$x .= '<br />'.$description.'<br />';
if ($criteria)
{
@@ -262,7 +266,7 @@ class AchievementList extends BaseType
$data[$this->id] = array(
"n" => $this->getField('name', true),
"s" => $this->curTpl['faction'],
"t" => TYPE_ACHIEVEMENT,
"t" => Type::ACHIEVEMENT,
"ti" => $this->id
);
}
@@ -317,8 +321,8 @@ class AchievementListFilter extends Filter
protected $inputFields = array(
'cr' => [FILTER_V_RANGE, [2, 18], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C};:]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name / description - only printable chars, no delimiter
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / description - only printable chars, no delimiter
'ex' => [FILTER_V_EQUAL, 'on', false], // extended name search
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'si' => [FILTER_V_LIST, [1, 2, 3, -1, -2], false], // side

View File

@@ -0,0 +1,102 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AreaTriggerList extends BaseType
{
use spawnHelper;
public static $type = Type::AREATRIGGER;
public static $brickFile = 'areatrigger';
public static $dataTable = '?_areatrigger';
protected $queryBase = 'SELECT a.*, a.id AS ARRAY_KEY FROM ?_areatrigger a';
protected $queryOpts = array(
'a' => [['s']],
's' => ['j' => ['?_spawns s ON s.type = 503 AND s.typeId = a.id', true], 's' => ', s.areaId']
);
public function __construct($conditions)
{
parent::__construct($conditions);
foreach ($this->iterate() as $id => &$_curTpl)
if (!$_curTpl['name'])
$_curTpl['name'] = 'Unnamed Areatrigger #' . $id;
}
public function getListviewData() : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->curTpl['id'],
'type' => $this->curTpl['type'],
'name' => $this->curTpl['name'],
);
if ($_ = $this->curTpl['areaId'])
$data[$this->id]['location'] = [$_];
}
return $data;
}
public function getJSGlobals($addMask = GLOBALINFO_ANY)
{
return [];
}
public function renderTooltip() { }
}
class AreaTriggerListFilter extends Filter
{
protected $genericFilter = array(
2 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT] // id
);
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'cr' => [FILTER_V_LIST, [2], true ], // criteria ids
'crs' => [FILTER_V_RANGE, [1, 6], true ], // criteria operators
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - all criteria are numeric here
'na' => [FILTER_V_REGEX, '/[\p{C};\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ty' => [FILTER_V_RANGE, [0, 5], true ] // types
);
protected function createSQLForCriterium(&$cr)
{
if (in_array($cr[0], array_keys($this->genericFilter)))
if ($genCr = $this->genericCriterion($cr))
return $genCr;
unset($cr);
$this->error = true;
return [1];
}
protected function createSQLForValues()
{
$parts = [];
$_v = &$this->fiData['v'];
// name [str]
if (isset($_v['na']))
if ($_ = $this->modularizeString(['name']))
$parts[] = $_;
// type [list]
if (isset($_v['ty']))
$parts[] = ['type', $_v['ty']];
return $parts;
}
}
?>

View File

@@ -17,7 +17,7 @@ class ArenaTeamList extends BaseType
{
$data[$this->id] = array(
'name' => $this->curTpl['name'],
'realm' => Profiler::urlize($this->curTpl['realmName']),
'realm' => Profiler::urlize($this->curTpl['realmName'], true),
'realmname' => $this->curTpl['realmName'],
// 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->curTpl['battlegroup'],
@@ -47,7 +47,7 @@ class ArenaTeamListFilter extends Filter
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
'si' => [FILTER_V_LIST, [1, 2], false], // side
@@ -88,7 +88,7 @@ class ArenaTeamListFilter extends Filter
protected function cbRegionCheck(&$v)
{
if ($v == 'eu' || $v == 'us')
if (in_array($v, Util::$regions))
{
$this->parentCats[0] = $v; // directly redirect onto this region
$v = ''; // remove from filter

View File

@@ -6,11 +6,11 @@ if (!defined('AOWOW_REVISION'))
class CharClassList extends BaseType
{
public static $type = TYPE_CLASS;
public static $type = Type::CHR_CLASS;
public static $brickFile = 'class';
public static $dataTable = '?_classes';
protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_classes c';
protected $queryBase = 'SELECT c.*, id AS ARRAY_KEY FROM ?_classes c';
public function __construct($conditions = [])
{

View File

@@ -6,11 +6,11 @@ if (!defined('AOWOW_REVISION'))
class CharRaceList extends BaseType
{
public static $type = TYPE_RACE;
public static $type = Type::CHR_RACE;
public static $brickFile = 'race';
public static $dataTable = '?_races';
protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_races r';
protected $queryBase = 'SELECT r.*, id AS ARRAY_KEY FROM ?_races r';
public function getListviewData()
{
@@ -40,7 +40,7 @@ class CharRaceList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_RACE][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::CHR_RACE][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}

View File

@@ -8,13 +8,13 @@ class CreatureList extends BaseType
{
use spawnHelper;
public static $type = TYPE_NPC;
public static $type = Type::NPC;
public static $brickFile = 'creature';
public static $dataTable = '?_creature';
protected $queryBase = 'SELECT ct.*, ct.id AS ARRAY_KEY FROM ?_creature ct';
public $queryOpts = array(
'ct' => [['ft', 'qse', 'dct1', 'dct2', 'dct3'], 's' => ', IFNULL(dct1.id, IFNULL(dct2.id, IFNULL(dct3.id, 0))) AS parentId, IFNULL(dct1.name_loc0, IFNULL(dct2.name_loc0, IFNULL(dct3.name_loc0, ""))) AS parent_loc0, IFNULL(dct1.name_loc2, IFNULL(dct2.name_loc2, IFNULL(dct3.name_loc2, ""))) AS parent_loc2, IFNULL(dct1.name_loc3, IFNULL(dct2.name_loc3, IFNULL(dct3.name_loc3, ""))) AS parent_loc3, IFNULL(dct1.name_loc6, IFNULL(dct2.name_loc6, IFNULL(dct3.name_loc6, ""))) AS parent_loc6, IFNULL(dct1.name_loc8, IFNULL(dct2.name_loc8, IFNULL(dct3.name_loc8, ""))) AS parent_loc8, IF(dct1.difficultyEntry1 = ct.id, 1, IF(dct2.difficultyEntry2 = ct.id, 2, IF(dct3.difficultyEntry3 = ct.id, 3, 0))) AS difficultyMode'],
'ct' => [['ft', 'qse', 'dct1', 'dct2', 'dct3'], 's' => ', IFNULL(dct1.id, IFNULL(dct2.id, IFNULL(dct3.id, 0))) AS parentId, IFNULL(dct1.name_loc0, IFNULL(dct2.name_loc0, IFNULL(dct3.name_loc0, ""))) AS parent_loc0, IFNULL(dct1.name_loc2, IFNULL(dct2.name_loc2, IFNULL(dct3.name_loc2, ""))) AS parent_loc2, IFNULL(dct1.name_loc3, IFNULL(dct2.name_loc3, IFNULL(dct3.name_loc3, ""))) AS parent_loc3, IFNULL(dct1.name_loc4, IFNULL(dct2.name_loc4, IFNULL(dct3.name_loc4, ""))) AS parent_loc4, IFNULL(dct1.name_loc6, IFNULL(dct2.name_loc6, IFNULL(dct3.name_loc6, ""))) AS parent_loc6, IFNULL(dct1.name_loc8, IFNULL(dct2.name_loc8, IFNULL(dct3.name_loc8, ""))) AS parent_loc8, IF(dct1.difficultyEntry1 = ct.id, 1, IF(dct2.difficultyEntry2 = ct.id, 2, IF(dct3.difficultyEntry3 = ct.id, 3, 0))) AS difficultyMode'],
'dct1' => ['j' => ['?_creature dct1 ON ct.cuFlags & 0x02 AND dct1.difficultyEntry1 = ct.id', true]],
'dct2' => ['j' => ['?_creature dct2 ON ct.cuFlags & 0x02 AND dct2.difficultyEntry2 = ct.id', true]],
'dct3' => ['j' => ['?_creature dct3 ON ct.cuFlags & 0x02 AND dct3.difficultyEntry3 = ct.id', true]],
@@ -49,7 +49,7 @@ class CreatureList extends BaseType
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_creature WHERE id = ?d', $id);
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_creature WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
@@ -81,10 +81,10 @@ class CreatureList extends BaseType
$row3[] = '('.$_.')';
$x = '<table>';
$x .= '<tr><td><b class="q">'.$this->getField('name', true).'</b></td></tr>';
$x .= '<tr><td><b class="q">'.Util::htmlEscape($this->getField('name', true)).'</b></td></tr>';
if ($sn = $this->getField('subname', true))
$x .= '<tr><td>'.$sn.'</td></tr>';
$x .= '<tr><td>'.Util::htmlEscape($sn).'</td></tr>';
$x .= '<tr><td>'.implode(' ', $row3).'</td></tr>';
@@ -102,27 +102,22 @@ class CreatureList extends BaseType
public function getRandomModelId()
{
// dwarf?? [null, 30754, 30753, 30755, 30736]
// totems use hardcoded models, tauren model is base
$totems = array( // tauren => [orc, dwarf(?!), troll, tauren, draenei]
4589 => [30758, 30754, 30762, 4589, 19074], // fire
4588 => [30757, 30753, 30761, 4588, 19073], // earth
4587 => [30759, 30755, 30763, 4587, 19075], // water
4590 => [30756, 30736, 30760, 4590, 19071], // air
);
$totems = [null, 4589, 4588, 4587, 4590]; // slot => modelId
$data = [];
for ($i = 1; $i < 5; $i++)
if ($_ = $this->curTpl['displayId'.$i])
$data[] = $_;
if (count($data) == 1 && in_array($data[0], array_keys($totems)))
$data = $totems[$data[0]];
if (count($data) == 1 && ($slotId = array_search($data[0], $totems)))
$data = DB::World()->selectCol('SELECT DisplayId FROM player_totem_model WHERE TotemSlot = ?d', $slotId);
return !$data ? 0 : $data[array_rand($data)];
}
public function getBaseStats($type)
public function getBaseStats(string $type) : array
{
// i'm aware of the BaseVariance/RangedVariance fields ... i'm just totaly unsure about the whole damage calculation
switch ($type)
@@ -147,8 +142,14 @@ class CreatureList extends BaseType
$rngMin = ($this->getField('dmgMin') + ($this->getField('rngAtkPwrMin') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed');
$rngMax = ($this->getField('dmgMax') * 1.5 + ($this->getField('rngAtkPwrMax') / 14)) * $this->getField('dmgMultiplier') * $this->getField('rngAtkSpeed');
return [$rngMin, $rngMax];
case 'resistance':
$r = [];
for ($i = SPELL_SCHOOL_HOLY; $i < SPELL_SCHOOL_ARCANE+1; $i++)
$r[$i] = $this->getField('resistance'.$i);
return $r;
default:
return [0, 0];
return [];
}
}
@@ -251,7 +252,7 @@ class CreatureList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_NPC][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::NPC][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
@@ -264,7 +265,7 @@ class CreatureList extends BaseType
{
$data[$this->id] = array(
'n' => $this->getField('parentId') ? $this->getField('parent', true) : $this->getField('name', true),
't' => TYPE_NPC,
't' => Type::NPC,
'ti' => $this->getField('parentId') ?: $this->id,
// 'bd' => (int)($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS || ($this->curTpl['typeFlags'] & 0x4 && $this->curTpl['rank']))
// 'z' where am i spawned
@@ -301,11 +302,11 @@ class CreatureListFilter extends Filter
7 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [enum]
8 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [enum]
9 => [FILTER_CR_BOOLEAN, 'lootId', ], // lootable
10 => [FILTER_CR_BOOLEAN, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn]
10 => [FILTER_CR_CALLBACK, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn]
11 => [FILTER_CR_BOOLEAN, 'pickpocketLootId', ], // pickpocketable
12 => [FILTER_CR_CALLBACK, 'cbMoneyDrop', null, null ], // averagemoneydropped [op] [int]
15 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_HERBLOOT, null ], // gatherable [yn]
16 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_ENGINEERLOOT, null ], // minable [yn]
16 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_MININGLOOT, null ], // minable [yn]
18 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_AUCTIONEER ], // auctioneer
19 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BANKER ], // banker
20 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BATTLEMASTER ], // battlemaster
@@ -328,15 +329,15 @@ class CreatureListFilter extends Filter
41 => [FILTER_CR_NYI_PH, 1, null ], // haslocation [yn] [staff]
42 => [FILTER_CR_CALLBACK, 'cbReputation', '>', null ], // increasesrepwith [enum]
43 => [FILTER_CR_CALLBACK, 'cbReputation', '<', null ], // decreasesrepwith [enum]
44 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_MININGLOOT, null ] // salvageable [yn]
44 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_ENGINEERLOOT, null ] // salvageable [yn]
);
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'cr' => [FILTER_V_LIST, [[1, 3],[5, 12], 15, 16, [18, 25], [27, 29], [31, 35], 37, 38, [40, 44]], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 9999]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C}:;]/ui', true ], // criteria values - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name / subname - only printable chars, no delimiter
'crv' => [FILTER_V_REGEX, '/[\p{C}:;%\\\\]/ui', true ], // criteria values - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / subname - only printable chars, no delimiter
'ex' => [FILTER_V_EQUAL, 'on', false], // also match subname
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'fa' => [FILTER_V_CALLBACK, 'cbPetFamily', true ], // pet family [list] - cat[0] == 1

View File

@@ -6,11 +6,11 @@ if (!defined('AOWOW_REVISION'))
class CurrencyList extends BaseType
{
public static $type = TYPE_CURRENCY;
public static $type = Type::CURRENCY;
public static $brickFile = 'currency';
public static $dataTable = '?_currencies';
protected $queryBase = 'SELECT *, c.id AS ARRAY_KEY FROM ?_currencies c';
protected $queryBase = 'SELECT c.*, c.id AS ARRAY_KEY FROM ?_currencies c';
protected $queryOpts = array(
'c' => [['ic']],
'ic' => ['j' => ['?_icons ic ON ic.id = c.iconId', true], 's' => ', ic.name AS iconString']
@@ -57,7 +57,7 @@ class CurrencyList extends BaseType
else
$icon = [$this->curTpl['iconString'], $this->curTpl['iconString']];
$data[TYPE_CURRENCY][$this->id] = ['name' => $this->getField('name', true), 'icon' => $icon];
$data[Type::CURRENCY][$this->id] = ['name' => $this->getField('name', true), 'icon' => $icon];
}
return $data;
@@ -69,11 +69,11 @@ class CurrencyList extends BaseType
return array();
$x = '<table><tr><td>';
$x .= '<b>'.Util::jsEscape($this->getField('name', true)).'</b><br>';
$x .= '<b>'.$this->getField('name', true).'</b><br>';
// cata+ (or go fill it by hand)
if ($_ = $this->getField('description', true))
$x .= '<div style="max-width: 300px" class="q">'.Util::jsEscape($_).'</div>';
$x .= '<div style="max-width: 300px" class="q">'.$_.'</div>';
if ($_ = $this->getField('cap'))
$x .= '<br><span class="q">'.Lang::currency('cap').Lang::main('colon').'</span>'.Lang::nf($_).'<br>';

View File

@@ -6,11 +6,11 @@ if (!defined('AOWOW_REVISION'))
class EmoteList extends BaseType
{
public static $type = TYPE_EMOTE;
public static $type = Type::EMOTE;
public static $brickFile = 'emote';
public static $dataTable = '?_emotes';
protected $queryBase = 'SELECT *, e.id AS ARRAY_KEY FROM ?_emotes e';
protected $queryBase = 'SELECT e.*, e.id AS ARRAY_KEY FROM ?_emotes e';
public function __construct($conditions = [])
{
@@ -47,7 +47,7 @@ class EmoteList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_EMOTE][$this->id] = ['name' => $this->getField('cmd')];
$data[Type::EMOTE][$this->id] = ['name' => $this->getField('cmd')];
return $data;
}

View File

@@ -8,7 +8,7 @@ class EnchantmentList extends BaseType
{
use listviewHelper;
public static $type = TYPE_ENCHANTMENT;
public static $type = Type::ENCHANTMENT;
public static $brickFile = 'enchantment';
public static $dataTable = '?_itemenchantment';
@@ -17,7 +17,7 @@ class EnchantmentList extends BaseType
private $triggerIds = [];
protected $queryBase = 'SELECT ie.*, ie.id AS ARRAY_KEY FROM ?_itemenchantment ie';
protected $queryOpts = array( // 502 => TYPE_ENCHANTMENT
protected $queryOpts = array( // 502 => Type::ENCHANTMENT
'ie' => [['is']],
'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 502 AND `is`.`typeId` = `ie`.`id`', true], 's' => ', `is`.*'],
);
@@ -73,7 +73,7 @@ class EnchantmentList extends BaseType
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_itemenchantment WHERE id = ?d', $id );
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_itemenchantment WHERE id = ?d', $id );
return Util::localizedString($n, 'name');
}
// end static use
@@ -216,7 +216,7 @@ class EnchantmentList extends BaseType
if ($addMask & GLOBALINFO_SELF)
foreach ($this->iterate() as $__)
$data[TYPE_ENCHANTMENT][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::ENCHANTMENT][$this->id] = ['name' => $this->getField('name', true)];
if ($addMask & GLOBALINFO_RELATED)
{
@@ -224,8 +224,8 @@ class EnchantmentList extends BaseType
$data = $this->relSpells->getJSGlobals(GLOBALINFO_SELF);
foreach ($this->triggerIds as $tId)
if (empty($data[TYPE_SPELL][$tId]))
$data[TYPE_SPELL][$tId] = $tId;
if (empty($data[Type::SPELL][$tId]))
$data[Type::SPELL][$tId] = $tId;
}
return $data;
@@ -310,7 +310,7 @@ class EnchantmentListFilter extends Filter
'cr' => [FILTER_V_RANGE, [2, 123], true ], // criteria ids
'crs' => [FILTER_V_RANGE, [1, 15], true ], // criteria operators
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - only numerals
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ty' => [FILTER_V_RANGE, [1, 8], true ] // types
);
@@ -338,13 +338,7 @@ class EnchantmentListFilter extends Filter
// type
if (isset($_v['ty']))
{
$_ = (array)$_v['ty'];
if (!array_diff($_, [1, 2, 3, 4, 5, 6, 7, 8]))
$parts[] = ['OR', ['type1', $_], ['type2', $_], ['type3', $_]];
else
unset($_v['ty']);
}
$parts[] = ['OR', ['type1', $_v['ty']], ['type2', $_v['ty']], ['type3', $_v['ty']]];
return $parts;
}

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class FactionList extends BaseType
{
public static $type = TYPE_FACTION;
public static $type = Type::FACTION;
public static $brickFile = 'faction';
public static $dataTable = '?_factions';
@@ -37,7 +37,7 @@ class FactionList extends BaseType
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_factions WHERE id = ?d', $id);
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_factions WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
@@ -75,7 +75,7 @@ class FactionList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_FACTION][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::FACTION][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}

View File

@@ -8,7 +8,7 @@ class GameObjectList extends BaseType
{
use listviewHelper, spawnHelper;
public static $type = TYPE_OBJECT;
public static $type = Type::OBJECT;
public static $brickFile = 'object';
public static $dataTable = '?_objects';
@@ -31,6 +31,9 @@ class GameObjectList extends BaseType
// post processing
foreach ($this->iterate() as $_id => &$curTpl)
{
if (!$curTpl['name_loc0'])
$curTpl['name_loc0'] = 'Unnamed Object #' . $_id;
// unpack miscInfo
$curTpl['lootStack'] = [];
$curTpl['spells'] = [];
@@ -59,7 +62,7 @@ class GameObjectList extends BaseType
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_objects WHERE id = ?d', $id);
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_objects WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
@@ -100,7 +103,7 @@ class GameObjectList extends BaseType
if (isset($this->curTpl['lockId']))
if ($locks = Lang::getLocks($this->curTpl['lockId']))
foreach ($locks as $l)
$x .= '<tr><td>'.$l.'</td></tr>';
$x .= '<tr><td>'.sprintf(Lang::game('requires'), $l).'</td></tr>';
$x .= '</table>';
@@ -112,7 +115,7 @@ class GameObjectList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_OBJECT][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::OBJECT][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}
@@ -125,7 +128,7 @@ class GameObjectList extends BaseType
{
$data[$this->id] = array(
'n' => $this->getField('name', true),
't' => TYPE_OBJECT,
't' => Type::OBJECT,
'ti' => $this->id
// 'bd' => bossdrop
// 'dd' => dungeondifficulty
@@ -140,6 +143,15 @@ class GameObjectList extends BaseType
class GameObjectListFilter extends Filter
{
public $extraOpts = [];
protected $enums = array(
50 => array(
null, 1, 2, 3, 4,
663 => 663,
883 => 883,
FILTER_ENUM_ANY => true,
FILTER_ENUM_NONE => false
)
);
protected $genericFilter = array(
1 => [FILTER_CR_ENUM, 's.areaId', null ], // foundin
@@ -152,15 +164,16 @@ class GameObjectListFilter extends Filter
13 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
15 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT ], // id
16 => [FILTER_CR_CALLBACK, 'cbRelEvent', null, null], // relatedevent (ignore removed by event)
18 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ] // hasvideos
18 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
50 => [FILTER_CR_ENUM, 'spellFocusId', null, ], // SpellFocus
);
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'cr' => [FILTER_V_LIST, [[1, 5], 7, 11, 13, 15, 16, 18], true ], // criteria ids
'cr' => [FILTER_V_LIST, [[1, 5], 7, 11, 13, 15, 16, 18, 50], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - only numeric input values expected
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false] // match any / all filter
);

View File

@@ -0,0 +1,167 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class GuideList extends BaseType
{
use ListviewHelper;
public const STATUS_COLORS = array(
GUIDE_STATUS_DRAFT => '#71D5FF',
GUIDE_STATUS_REVIEW => '#FFFF00',
GUIDE_STATUS_APPROVED => '#1EFF00',
GUIDE_STATUS_REJECTED => '#FF4040',
GUIDE_STATUS_ARCHIVED => '#FFD100'
);
public static $type = Type::GUIDE;
public static $brickFile = 'guide';
public static $dataTable = '?_guides';
private $article = [];
private $jsGlobals = [];
protected $queryBase = 'SELECT g.*, g.id AS ARRAY_KEY FROM ?_guides g';
protected $queryOpts = array(
'g' => [['a', 'c'], 'g' => 'g.`id`'],
'a' => ['j' => ['?_account a ON a.id = g.userId', true], 's' => ', IFNULL(a.displayName, "") AS author'],
'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS `comments`']
);
public function __construct($conditions = [])
{
parent::__construct($conditions);
if ($this->error)
return;
$ratings = DB::Aowow()->select('SELECT `entry` AS ARRAY_KEY, IFNULL(SUM(`value`), 0) AS `t`, IFNULL(COUNT(*), 0) AS `n`, IFNULL(MAX(IF(`userId` = ?d, `value`, 0)), 0) AS `s` FROM ?_user_ratings WHERE `type` = ?d AND `entry` IN (?a)', User::$id, RATING_GUIDE, $this->getFoundIDs());
// post processing
foreach ($this->iterate() as $id => &$_curTpl)
{
if (isset($ratings[$id]))
{
$_curTpl['nvotes'] = $ratings[$id]['n'];
$_curTpl['rating'] = $ratings[$id]['n'] < 5 ? -1 : $ratings[$id]['t'] / $ratings[$id]['n'];
$_curTpl['_self'] = $ratings[$id]['s'];
}
else
{
$_curTpl['nvotes'] = 0;
$_curTpl['rating'] = -1;
}
}
}
public function getArticle(int $rev = -1) : string
{
if ($rev < -1)
$rev = -1;
if (empty($this->article[$rev]))
{
$a = DB::Aowow()->selectRow('SELECT `article`, `rev` FROM ?_articles WHERE ((`type` = ?d AND `typeId` = ?d){ OR `url` = ?}){ AND `rev`= ?d} ORDER BY `rev` DESC LIMIT 1',
Type::GUIDE, $this->id, $this->getField('url') ?: DBSIMPLE_SKIP, $rev < 0 ? DBSIMPLE_SKIP : $rev);
$this->article[$a['rev']] = $a['article'];
if ($this->article[$a['rev']])
{
(new Markup($this->article[$a['rev']]))->parseGlobalsFromText($this->jsGlobals);
return $this->article[$a['rev']];
}
else
trigger_error('GuideList::getArticle - linked article is missing');
}
return $this->article[$rev] ?? '';
}
public function getListviewData(bool $addDescription = false) : array
{
$data = [];
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'id' => $this->id,
'category' => $this->getField('category'),
'title' => $this->getField('title'),
'description' => $this->getField('description'),
'sticky' => !!($this->getField('cuFlags') & CC_FLAG_STICKY),
'nvotes' => $this->getField('nvotes'),
'url' => '/?guide=' . ($this->getField('url') ?: $this->id),
'status' => $this->getField('status'),
'author' => $this->getField('author'),
'authorroles' => $this->getField('roles'),
'rating' => $this->getField('rating'),
'views' => $this->getField('views'),
'comments' => $this->getField('comments'),
// 'patch' => $this->getField(''), // 30305 - patch is pointless, use date instead
'date' => $this->getField('date'), // ok
'when' => date(Util::$dateFormatInternal, $this->getField('date'))
);
}
return $data;
}
public function userCanView() : bool
{
// is owner || is staff
return $this->getField('userId') == User::$id || User::isInGroup(U_GROUP_STAFF);
}
public function canBeViewed() : bool
{
// currently approved || has prev. approved version
return $this->getField('status') == GUIDE_STATUS_APPROVED || $this->getField('rev') > 0;
}
public function canBeReported() : bool
{
// not own guide && is not archived
return $this->getField('userId') != User::$id && $this->getField('status') != GUIDE_STATUS_ARCHIVED;
}
public function getJSGlobals($addMask = GLOBALINFO_ANY) : array
{
return $this->jsGlobals;
}
public function renderTooltip() : string
{
$specStr = '';
if ($this->getField('classId') && $this->getField('category') == 1)
{
$c = $this->getField('classId');
if (($s = $this->getField('specId')) > -1)
{
$i = Game::$specIconStrings[$c][$s];
$n = Lang::game('classSpecs', $c, $s);
}
else
{
$i = 'class_'.Game::$classFileStrings[$c];
$n = Lang::game('cl', $c);
}
$specStr = '&nbsp;&nbsp;&nbsp;&nbsp;<span class="icontiny c'.$c.'" style="background-image: url('.STATIC_URL.'/images/wow/icons/tiny/'.$i.'.gif)">'.$n.'</span>';
}
$tt = '<table><tr><td><div style="max-width: 320px"><b class="q">'.$this->getField('title').'</b><br>';
$tt .= '<table width="100%"><tr><td>'.Lang::guide('guide').'</td><th>'.Lang::guide('byAuthor', [$this->getField('author')]).'</th></tr></table>';
$tt .= '<table width="100%"><tr><td>'.Lang::guide('category', $this->getField('category')).$specStr.'</td><th>'.Lang::guide('patch').' 3.3.5</th></tr></table>';
$tt .= '<div class="q" style="margin: 0.25em 0">'.$this->getField('description').'</div>';
$tt .= '</div></td></tr></table>';
return $tt;
}
}
?>

View File

@@ -16,12 +16,12 @@ class GuildList extends BaseType
foreach ($this->iterate() as $__)
{
$data[$this->id] = array(
'name' => "$'".$this->curTpl['name']."'", // MUST be a string
'name' => '$"'.str_replace ('"', '', $this->curTpl['name']).'"', // MUST be a string, omit any quotes in name
'members' => $this->curTpl['members'],
'faction' => $this->curTpl['faction'],
'achievementpoints' => $this->getField('achievementpoints'),
'gearscore' => $this->getField('gearscore'),
'realm' => Profiler::urlize($this->curTpl['realmName']),
'realm' => Profiler::urlize($this->curTpl['realmName'], true),
'realmname' => $this->curTpl['realmName'],
// 'battlegroup' => Profiler::urlize($this->curTpl['battlegroup']), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->curTpl['battlegroup'],
@@ -90,7 +90,7 @@ class GuildListFilter extends Filter
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
'si' => [FILTER_V_LIST, [1, 2], false], // side
@@ -126,7 +126,7 @@ class GuildListFilter extends Filter
protected function cbRegionCheck(&$v)
{
if ($v == 'eu' || $v == 'us')
if (in_array($v, Util::$regions))
{
$this->parentCats[0] = $v; // directly redirect onto this region
$v = ''; // remove from filter

View File

@@ -8,7 +8,7 @@ class IconList extends BaseType
{
use listviewHelper;
public static $type = TYPE_ICON;
public static $type = Type::ICON;
public static $brickFile = 'icon';
public static $dataTable = '?_icons';
public static $contribute = CONTRIBUTE_CO;
@@ -24,7 +24,7 @@ class IconList extends BaseType
protected $queryBase = 'SELECT ic.*, ic.id AS ARRAY_KEY FROM ?_icons ic';
/* this works, but takes ~100x more time than i'm comfortable with .. kept as reference
protected $queryOpts = array( // 29 => TYPE_ICON
protected $queryOpts = array( // 29 => Type::ICON
'ic' => [['s', 'i', 'a', 'c', 'p'], 'g' => 'ic.id'],
'i' => ['j' => ['?_items `i` ON `i`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `i`.`id`) AS nItems'],
's' => ['j' => ['?_spell `s` ON `s`.`iconId` = `ic`.`id`', true], 's' => ', COUNT(DISTINCT `s`.`id`) AS nSpells'],
@@ -90,7 +90,7 @@ class IconList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_ICON][$this->id] = ['name' => $this->getField('name', true, true), 'icon' => $this->getField('name', true, true)];
$data[Type::ICON][$this->id] = ['name' => $this->getField('name', true, true), 'icon' => $this->getField('name', true, true)];
return $data;
}
@@ -135,7 +135,7 @@ class IconListFilter extends Filter
'cr' => [FILTER_V_LIST, [1, 2, 3, 6, 9, 11, 13], true ], // criteria ids
'crs' => [FILTER_V_RANGE, [1, 6], true ], // criteria operators
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - all criteria are numeric here
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false] // match any / all filter
);

View File

@@ -8,7 +8,7 @@ class ItemList extends BaseType
{
use ListviewHelper;
public static $type = TYPE_ITEM;
public static $type = Type::ITEM;
public static $brickFile = 'item';
public static $dataTable = '?_items';
@@ -25,7 +25,7 @@ class ItemList extends BaseType
private $jsGlobals = []; // getExtendedCost creates some and has no access to template
protected $queryBase = 'SELECT i.*, i.block AS tplBlock, i.id AS ARRAY_KEY, i.id AS id FROM ?_items i';
protected $queryOpts = array( // 3 => TYPE_ITEM
protected $queryOpts = array( // 3 => Type::ITEM
'i' => [['is', 'src', 'ic'], 'o' => 'i.quality DESC, i.itemLevel DESC'],
'ic' => ['j' => ['?_icons `ic` ON `ic`.`id` = `i`.`iconId`', true], 's' => ', ic.name AS iconString'],
'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 3 AND `is`.`typeId` = `i`.`id`', true], 's' => ', `is`.*'],
@@ -46,10 +46,17 @@ class ItemList extends BaseType
$this->initJsonStats();
if ($miscData)
{
// readdress itemset .. is wrong for virtual sets
if ($miscData && isset($miscData['pcsToSet']) && isset($miscData['pcsToSet'][$this->id]))
if (isset($miscData['pcsToSet']) && isset($miscData['pcsToSet'][$this->id]))
$this->json[$this->id]['itemset'] = $miscData['pcsToSet'][$this->id];
// additional rel attribute for listview rows
if (isset($miscData['extraOpts']['relEnchant']))
$this->relEnchant = $miscData['extraOpts']['relEnchant'];
}
// unify those pesky masks
$_ = &$_curTpl['requiredClass'];
$_ &= CLASS_MASK_ALL;
@@ -77,7 +84,7 @@ class ItemList extends BaseType
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->selectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_items WHERE id = ?d', $id);
$n = DB::Aowow()->selectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_items WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
@@ -88,33 +95,46 @@ class ItemList extends BaseType
if ($this->error)
return [];
$idx = $this->id;
if (empty($this->vendors))
{
$itemz = DB::World()->select('
SELECT nv.item AS ARRAY_KEY1, nv.entry AS ARRAY_KEY2, 0 AS eventId, nv.maxcount, nv.extendedCost FROM npc_vendor nv WHERE {nv.entry IN (?a) AND} nv.item IN (?a)
$itemz = [];
$xCostData = [];
$rawEntries = DB::World()->select('
SELECT nv.item, nv.entry, 0 AS eventId, nv.maxcount, nv.extendedCost FROM npc_vendor nv WHERE {nv.entry IN (?a) AND} nv.item IN (?a)
UNION
SELECT genv.item AS ARRAY_KEY1, c.id AS ARRAY_KEY2, ge.eventEntry AS eventId, genv.maxcount, genv.extendedCost FROM game_event_npc_vendor genv LEFT JOIN game_event ge ON genv.eventEntry = ge.eventEntry JOIN creature c ON c.guid = genv.guid WHERE {c.id IN (?a) AND} genv.item IN (?a)',
empty($filter[TYPE_NPC]) || !is_array($filter[TYPE_NPC]) ? DBSIMPLE_SKIP : $filter[TYPE_NPC],
SELECT genv.item, c.id AS `entry`, ge.eventEntry AS eventId, genv.maxcount, genv.extendedCost FROM game_event_npc_vendor genv LEFT JOIN game_event ge ON genv.eventEntry = ge.eventEntry JOIN creature c ON c.guid = genv.guid WHERE {c.id IN (?a) AND} genv.item IN (?a)',
empty($filter[Type::NPC]) || !is_array($filter[Type::NPC]) ? DBSIMPLE_SKIP : $filter[Type::NPC],
array_keys($this->templates),
empty($filter[TYPE_NPC]) || !is_array($filter[TYPE_NPC]) ? DBSIMPLE_SKIP : $filter[TYPE_NPC],
empty($filter[Type::NPC]) || !is_array($filter[Type::NPC]) ? DBSIMPLE_SKIP : $filter[Type::NPC],
array_keys($this->templates)
);
$xCosts = [];
foreach ($itemz as $i => $vendors)
$xCosts = array_merge($xCosts, array_column($vendors, 'extendedCost'));
foreach ($rawEntries as $costEntry)
{
if ($costEntry['extendedCost'])
$xCostData[] = $costEntry['extendedCost'];
if ($xCosts)
$xCosts = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_itemextendedcost WHERE id IN (?a)', $xCosts);
if (!isset($itemz[$costEntry['item']][$costEntry['entry']]))
$itemz[$costEntry['item']][$costEntry['entry']] = [$costEntry];
else
$itemz[$costEntry['item']][$costEntry['entry']][] = $costEntry;
}
if ($xCostData)
$xCostData = DB::Aowow()->select('SELECT *, id AS ARRAY_KEY FROM ?_itemextendedcost WHERE id IN (?a)', $xCostData);
$cItems = [];
foreach ($itemz as $k => $vendors)
{
foreach ($vendors as $l => $vInfo)
foreach ($vendors as $l => $vendor)
{
foreach ($vendor as $m => $vInfo)
{
$costs = [];
if (!empty($xCosts[$vInfo['extendedCost']]))
$costs = $xCosts[$vInfo['extendedCost']];
if (!empty($xCostData[$vInfo['extendedCost']]))
$costs = $xCostData[$vInfo['extendedCost']];
$data = array(
'stock' => $vInfo['maxcount'] ?: -1,
@@ -127,13 +147,13 @@ class ItemList extends BaseType
if (!empty($costs['reqArenaPoints']))
{
$data[-103] = $costs['reqArenaPoints'];
$this->jsGlobals[TYPE_CURRENCY][103] = 103;
$this->jsGlobals[Type::CURRENCY][103] = 103;
}
if (!empty($costs['reqHonorPoints']))
{
$data[-104] = $costs['reqHonorPoints'];
$this->jsGlobals[TYPE_CURRENCY][104] = 104;
$this->jsGlobals[Type::CURRENCY][104] = 104;
}
for ($i = 1; $i < 6; $i++)
@@ -147,10 +167,15 @@ class ItemList extends BaseType
// no extended cost or additional gold required
if (!$costs || $this->getField('flagsExtra') & 0x04)
{
$this->getEntry($k);
if ($_ = $this->getField('buyPrice'))
$data[0] = $_;
}
$vendors[$l] = $data;
$vendor[$m] = $data;
}
$vendors[$l] = $vendor;
}
$itemz[$k] = $vendors;
@@ -164,11 +189,13 @@ class ItemList extends BaseType
foreach ($jsData as $k => $v)
$this->jsGlobals[$type][$k] = $v;
foreach ($itemz as $id => $vendors)
foreach ($itemz as $itemId => $vendors)
{
foreach ($vendors as $l => $costs)
foreach ($vendors as $npcId => $costData)
{
foreach ($costs as $k => $v)
foreach ($costData as $itr => $cost)
{
foreach ($cost as $k => $v)
{
if (in_array($k, $cItems))
{
@@ -177,20 +204,22 @@ class ItemList extends BaseType
{
if ($moneyItems->getField('itemId') == $k)
{
unset($costs[$k]);
$costs[-$moneyItems->id] = $v;
unset($cost[$k]);
$cost[-$moneyItems->id] = $v;
$found = true;
break;
}
}
if (!$found)
$this->jsGlobals[TYPE_ITEM][$k] = $k;
$this->jsGlobals[Type::ITEM][$k] = $k;
}
}
$vendors[$l] = $costs;
$costData[$itr] = $cost;
}
$itemz[$id] = $vendors;
$vendors[$npcId] = $costData;
}
$itemz[$itemId] = $vendors;
}
}
@@ -200,13 +229,15 @@ class ItemList extends BaseType
$result = $this->vendors;
// apply filter if given
$tok = !empty($filter[TYPE_ITEM]) ? $filter[TYPE_ITEM] : null;
$cur = !empty($filter[TYPE_CURRENCY]) ? $filter[TYPE_CURRENCY] : null;
$tok = !empty($filter[Type::ITEM]) ? $filter[Type::ITEM] : null;
$cur = !empty($filter[Type::CURRENCY]) ? $filter[Type::CURRENCY] : null;
foreach ($result as $itemId => &$data)
{
$reqRating = [];
foreach ($data as $npcId => $costs)
foreach ($data as $npcId => $entries)
{
foreach ($entries as $costs)
{
if ($tok || $cur) // bought with specific token or currency
{
@@ -229,14 +260,15 @@ class ItemList extends BaseType
if (isset($data[$npcId]) && $costs['reqRating'] && (!$reqRating || $reqRating[0] < $costs['reqRating']))
$reqRating = [$costs['reqRating'], $costs['reqBracket']];
}
if ($reqRating)
$data['reqRating'] = $reqRating[0];
}
if (empty($data))
unset($result[$itemId]);
}
// restore internal index;
$this->getEntry($idx);
return $result;
}
@@ -259,6 +291,11 @@ class ItemList extends BaseType
if ($addInfoMask & ITEMINFO_JSON)
$this->extendJsonStats();
$extCosts = [];
if ($addInfoMask & ITEMINFO_VENDOR)
$extCosts = $this->getExtendedCost($miscData);
$extCostOther = [];
foreach ($this->iterate() as $__)
{
foreach ($this->json[$this->id] as $k => $v)
@@ -268,6 +305,15 @@ class ItemList extends BaseType
$data[$this->id]['name'] = $data[$this->id]['quality'].$data[$this->id]['name'];
unset($data[$this->id]['quality']);
if (!empty($this->relEnchant) && $this->curTpl['randomEnchant'])
{
if (($x = array_search($this->curTpl['randomEnchant'], array_column($this->relEnchant, 'entry'))) !== false)
{
$data[$this->id]['rel'] = 'rand='.$this->relEnchant[$x]['ench'];
$data[$this->id]['name'] .= ' '.$this->relEnchant[$x]['name'];
}
}
if ($addInfoMask & ITEMINFO_JSON)
{
foreach ($this->itemMods[$this->id] as $k => $v)
@@ -293,14 +339,17 @@ class ItemList extends BaseType
if ($addInfoMask & ITEMINFO_VENDOR)
{
// just use the first results
// todo (med): dont use first result; search for the right one
if (!empty($this->getExtendedCost($miscData)[$this->id]))
// todo (med): dont use first vendor; search for the right one
if (!empty($extCosts[$this->id]))
{
$cost = reset($extCosts[$this->id]);
foreach ($cost as $itr => $entries)
{
$cost = reset($this->getExtendedCost($miscData)[$this->id]);
$currency = [];
$tokens = [];
$costArr = [];
foreach ($cost as $k => $qty)
foreach ($entries as $k => $qty)
{
if (is_string($k))
continue;
@@ -311,24 +360,30 @@ class ItemList extends BaseType
$currency[] = [-$k, $qty];
}
$data[$this->id]['stock'] = $cost['stock']; // display as column in lv
$data[$this->id]['avail'] = $cost['stock']; // display as number on icon
$data[$this->id]['cost'] = [empty($cost[0]) ? 0 : $cost[0]];
$costArr['stock'] = $entries['stock'];// display as column in lv
$costArr['avail'] = $entries['stock'];// display as number on icon
$costArr['cost'] = [empty($entries[0]) ? 0 : $entries[0]];
if ($cost['event'])
if ($entries['event'])
{
$this->jsGlobals[TYPE_WORLDEVENT][$cost['event']] = $cost['event'];
$row['condition'][0][$this->id][] = [[CND_ACTIVE_EVENT, $cost['event']]];
$this->jsGlobals[Type::WORLDEVENT][$entries['event']] = $entries['event'];
$costArr['condition'][0][$this->id][] = [[CND_ACTIVE_EVENT, $entries['event']]];
}
if ($currency || $tokens) // fill idx:3 if required
$data[$this->id]['cost'][] = $currency;
$costArr['cost'][] = $currency;
if ($tokens)
$data[$this->id]['cost'][] = $tokens;
$costArr['cost'][] = $tokens;
if (!empty($cost['reqRating']))
$data[$this->id]['reqarenartng'] = $cost['reqRating'];
if (!empty($entries['reqRating']))
$costArr['reqarenartng'] = $entries['reqRating'];
if ($itr > 0)
$extCostOther[$this->id][] = $costArr;
else
$data[$this->id] = array_merge($data[$this->id], $costArr);
}
}
if ($x = $this->curTpl['buyPrice'])
@@ -390,6 +445,10 @@ class ItemList extends BaseType
$data[$this->id]['cooldown'] = $this->curTpl['cooldown'] / 1000;
}
foreach ($extCostOther as $itemId => $duplicates)
foreach ($duplicates as $d)
$data[] = array_merge($data[$itemId], $d); // we dont really use keys on data, but this may cause errors in future
/* even more complicated crap
modelviewer {type:X, displayid:Y, slot:z} .. not sure, when to set
*/
@@ -405,7 +464,7 @@ class ItemList extends BaseType
{
if ($addMask & GLOBALINFO_SELF)
{
$data[TYPE_ITEM][$id] = array(
$data[Type::ITEM][$id] = array(
'name' => $this->getField('name', true),
'quality' => $this->curTpl['quality'],
'icon' => $this->curTpl['iconString']
@@ -471,7 +530,7 @@ class ItemList extends BaseType
if ($this->enhanceR['enchantId'.$i] <= 0)
continue;
$enchant = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE Id = ?d', $this->enhanceR['enchantId'.$i]);
$enchant = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?d', $this->enhanceR['enchantId'.$i]);
if ($this->enhanceR['allocationPct'.$i] > 0)
{
$amount = intVal($this->enhanceR['allocationPct'.$i] * $this->generateEnchSuffixFactor());
@@ -640,10 +699,10 @@ class ItemList extends BaseType
if ($interactive)
$spanI = 'class="q2 tip" onmouseover="$WH.Tooltip.showAtCursor(event, $WH.sprintf(LANG.tooltip_armorbonus, '.$this->curTpl['armorDamageModifier'].'), 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"';
$x .= '<span '.$spanI.'><!--addamr'.$this->curTpl['armorDamageModifier'].'--><span>'.Lang::item('armor', [intVal($this->curTpl['armor'] - $this->curTpl['armorDamageModifier'])]).'</span></span><br />';
$x .= '<span '.$spanI.'><!--addamr'.$this->curTpl['armorDamageModifier'].'--><span>'.Lang::item('armor', [$this->curTpl['armor']]).'</span></span><br />';
}
else if (($this->curTpl['armor'] - $this->curTpl['armorDamageModifier']) > 0)
$x .= '<span><!--amr-->'.Lang::item('armor', [intVal($this->curTpl['armor'] - $this->curTpl['armorDamageModifier'])]).'</span><br />';
else if ($this->curTpl['armor'])
$x .= '<span><!--amr-->'.Lang::item('armor', [$this->curTpl['armor']]).'</span><br />';
// Block (note: block value from field block and from field stats or parsed from itemSpells are displayed independently)
if ($this->curTpl['tplBlock'])
@@ -676,7 +735,7 @@ class ItemList extends BaseType
$vspfArgs = [Lang::item('gemColors', $gemCnd['color'.$i] - 1), Lang::item('gemColors', $gemCnd['cmpColor'.$i] - 1)];
break;
default:
continue;
continue 2;
}
$x .= '<span class="q0">'.Lang::achievement('reqNumCrt').' '.Lang::item('gemConditions', $gemCnd['comparator'.$i], $vspfArgs).'</span><br />';
@@ -727,7 +786,7 @@ class ItemList extends BaseType
// Enchantment
if (isset($enhance['e']))
{
if ($enchText = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE Id = ?', $enhance['e']))
if ($enchText = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?', $enhance['e']))
$x .= '<span class="q2"><!--e-->'.Util::localizedString($enchText, 'name').'</span><br />';
else
{
@@ -806,32 +865,32 @@ class ItemList extends BaseType
if ($_ = $this->curTpl['socketBonus'])
{
$sbonus = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE Id = ?d', $_);
$x .= '<span class="q'.($hasMatch ? '2' : '0').'">'.Lang::item('socketBonus').Lang::main('colon').'<a href="?enchantment='.$_.'">'.Util::localizedString($sbonus, 'name').'</a></span><br />';
$sbonus = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE id = ?d', $_);
$x .= '<span class="q'.($hasMatch ? '2' : '0').'">'.Lang::item('socketBonus', ['<a href="?enchantment='.$_.'">'.Util::localizedString($sbonus, 'name').'</a>']).'</span><br />';
}
// durability
if ($dur = $this->curTpl['durability'])
$x .= sprintf(Lang::item('durability'), $dur, $dur).'<br />';
$jsg = [];
// required classes
if ($classes = Lang::getClassString($this->curTpl['requiredClass'], $jsg, $__))
if ($classes = Lang::getClassString($this->curTpl['requiredClass'], $jsg))
{
foreach ($jsg as $js)
if (empty($this->jsGlobals[TYPE_CLASS][$js]))
$this->jsGlobals[TYPE_CLASS][$js] = $js;
if (empty($this->jsGlobals[Type::CHR_CLASS][$js]))
$this->jsGlobals[Type::CHR_CLASS][$js] = $js;
$x .= Lang::game('classes').Lang::main('colon').$classes.'<br />';
}
// required races
if ($races = Lang::getRaceString($this->curTpl['requiredRace'], $__, $jsg, $__))
if ($races = Lang::getRaceString($this->curTpl['requiredRace'], $jsg))
{
foreach ($jsg as $js)
if (empty($this->jsGlobals[TYPE_RACE][$js]))
$this->jsGlobals[TYPE_RACE][$js] = $js;
if (empty($this->jsGlobals[Type::CHR_RACE][$js]))
$this->jsGlobals[Type::CHR_ACE][$js] = $js;
if ($races != Lang::game('ra', 0)) // not "both", but display combinations like: troll, dwarf
$x .= Lang::game('races').Lang::main('colon').$races.'<br />';
}
@@ -875,8 +934,8 @@ class ItemList extends BaseType
$x .= sprintf(Lang::game('requires'), '<a class="q1" href="?faction='.$reqFac.'">'.FactionList::getName($reqFac).'</a> - '.Lang::game('rep', $this->curTpl['requiredFactionRank'])).'<br />';
// locked or openable
if ($locks = Lang::getLocks($this->curTpl['lockId'], true))
$x .= '<span class="q0">'.Lang::item('locked').'<br />'.implode('<br />', $locks).'</span><br />';
if ($locks = Lang::getLocks($this->curTpl['lockId'], $arr, true, true))
$x .= '<span class="q0">'.Lang::item('locked').'<br />'.implode('<br />', array_map(function($x) { return sprintf(Lang::game('requires'), $x); }, $locks)).'</span><br />';
else if ($this->curTpl['flags'] & ITEM_FLAG_OPENABLE)
$x .= '<span class="q2">'.Lang::item('openClick').'</span><br />';
@@ -896,9 +955,14 @@ class ItemList extends BaseType
if ($cd < $this->curTpl['spellCategoryCooldown'.$j])
$cd = $this->curTpl['spellCategoryCooldown'.$j];
$cd = $cd < 5000 ? null : ' ('.sprintf(Lang::game('cooldown'), Util::formatTime($cd)).')';
$extra = [];
if ($cd >= 5000)
$extra[] = Lang::game('cooldown', [Util::formatTime($cd, true)]);
if ($this->curTpl['spellTrigger'.$j] == 2)
if ($ppm = $this->curTpl['spellppmRate'.$j])
$extra[] = Lang::spell('ppm', [$ppm]);
$itemSpellsAndTrigger[$this->curTpl['spellId'.$j]] = [$this->curTpl['spellTrigger'.$j], $cd];
$itemSpellsAndTrigger[$this->curTpl['spellId'.$j]] = [$this->curTpl['spellTrigger'.$j], $extra ? ' ('.implode(', ', $extra).')' : ''];
}
}
@@ -942,12 +1006,33 @@ class ItemList extends BaseType
$pieces = [];
if ($setId = $this->getField('itemset'))
{
// while Ids can technically be used multiple times the only difference in data are the items used. So it doesn't matter what we get
$itemset = new ItemsetList(array(['id', $setId]));
$condition = [
['refSetId', $setId],
// ['quality', $this->curTpl['quality']],
['minLevel', $this->curTpl['itemLevel'], '<='],
['maxLevel', $this->curTpl['itemLevel'], '>=']
];
$itemset = new ItemsetList($condition);
if (!$itemset->error && $itemset->pieceToSet)
{
// handle special cases where:
// > itemset has items of different qualities (handled by not limiting for this in the initial query)
// > itemset is virtual and multiple instances have the same itemLevel but not quality (filter below)
if ($itemset->getMatches() > 1)
{
foreach ($itemset->iterate() as $id => $__)
{
if ($itemset->getField('quality') == $this->curTpl['quality'])
{
$itemset->pieceToSet = array_filter($itemset->pieceToSet, function($x) use ($id) { return $id == $x; });
break;
}
}
}
$pieces = DB::Aowow()->select('
SELECT b.id AS ARRAY_KEY, b.name_loc0, b.name_loc2, b.name_loc3, b.name_loc6, b.name_loc8, GROUP_CONCAT(a.id SEPARATOR \':\') AS equiv
SELECT b.id AS ARRAY_KEY, b.name_loc0, b.name_loc2, b.name_loc3, b.name_loc4, b.name_loc6, b.name_loc8, GROUP_CONCAT(a.id SEPARATOR \':\') AS equiv
FROM ?_items a, ?_items b
WHERE a.slotBak = b.slotBak AND a.itemset = b.itemset AND b.id IN (?a)
GROUP BY b.id;',
@@ -1115,7 +1200,7 @@ class ItemList extends BaseType
// is it available for this item? .. does it even exist?!
if (empty($this->enhanceR))
if (DB::World()->selectCell('SELECT 1 FROM item_enchantment_template WHERE entry = ?d AND ench = ?d', abs($this->getField('randomEnchant')), abs($randId)))
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomenchant WHERE Id = ?d', $randId))
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomenchant WHERE id = ?d', $randId))
$this->enhanceR = $_;
return !empty($this->enhanceR);
@@ -1124,7 +1209,7 @@ class ItemList extends BaseType
// from Trinity
public function generateEnchSuffixFactor()
{
$rpp = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomproppoints WHERE Id = ?', $this->curTpl['itemLevel']);
$rpp = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomproppoints WHERE id = ?', $this->curTpl['itemLevel']);
if (!$rpp)
return 0;
@@ -1203,15 +1288,8 @@ class ItemList extends BaseType
$this->itemMods[$this->id] = [];
foreach (Game::$itemMods as $mod)
{
if (isset($this->curTpl[$mod]) && ($_ = floatVal($this->curTpl[$mod])))
{
if (!isset($this->itemMods[$this->id][$mod]))
$this->itemMods[$this->id][$mod] = 0;
$this->itemMods[$this->id][$mod] += $_;
}
}
if ($_ = floatVal($this->curTpl[$mod]))
Util::arraySumByKey($this->itemMods[$this->id], [$mod => $_]);
// fetch and add socketbonusstats
if (!empty($this->json[$this->id]['socketbonus']))
@@ -1224,7 +1302,7 @@ class ItemList extends BaseType
if ($enchantments)
{
$eStats = DB::Aowow()->select('SELECT *, typeId AS ARRAY_KEY FROM ?_item_stats WHERE `type` = ?d AND typeId IN (?a)', TYPE_ENCHANTMENT, array_keys($enchantments));
$eStats = DB::Aowow()->select('SELECT *, typeId AS ARRAY_KEY FROM ?_item_stats WHERE `type` = ?d AND typeId IN (?a)', Type::ENCHANTMENT, array_keys($enchantments));
Util::checkNumeric($eStats);
// and merge enchantments back
@@ -1238,7 +1316,7 @@ class ItemList extends BaseType
if ($item > 0) // apply socketBonus
$this->json[$item]['socketbonusstat'] = array_filter($eStats[$eId]);
else /* if ($item < 0) */ // apply gemEnchantment
Util::arraySumByKey($this->json[-$item][$mod], array_filter($eStats[$eId]));
Util::arraySumByKey($this->json[-$item], array_filter($eStats[$eId]));
}
}
}
@@ -1284,7 +1362,7 @@ class ItemList extends BaseType
{
$data[$this->id] = array(
'n' => $this->getField('name', true),
't' => TYPE_ITEM,
't' => Type::ITEM,
'ti' => $this->id,
'q' => $this->curTpl['quality'],
// 'p' => PvP [NYI]
@@ -1346,7 +1424,7 @@ class ItemList extends BaseType
$buff[$_curTpl['moreType']][] = $_curTpl['moreTypeId'];
foreach ($buff as $type => $ids)
$this->sourceMore[$type] = (new Util::$typeClasses[$type](array(['id', $ids])))->getSourceData();
$this->sourceMore[$type] = (Type::newList($type, [['id', $ids]]))?->getSourceData();
}
$s = array_keys($this->sources[$this->id]);
@@ -1606,7 +1684,7 @@ class ItemList extends BaseType
'frores' => $this->curTpl['resFrost'],
'shares' => $this->curTpl['resShadow'],
'arcres' => $this->curTpl['resArcane'],
'armorbonus' => $this->curTpl['armorDamageModifier'],
'armorbonus' => max(0, intVal($this->curTpl['armorDamageModifier'])),
'armor' => $this->curTpl['armor'],
'dura' => $this->curTpl['durability'],
'itemset' => $this->curTpl['itemset'],
@@ -1647,9 +1725,6 @@ class ItemList extends BaseType
$json['feratkpwr'] = $fap;
}
if ($this->curTpl['armorDamageModifier'] > 0)
$json['armor'] -= $this->curTpl['armorDamageModifier'];
if ($this->curTpl['class'] == ITEM_CLASS_ARMOR || $this->curTpl['class'] == ITEM_CLASS_WEAPON)
$json['gearscore'] = Util::getEquipmentScore($json['level'], $this->getField('quality'), $json['slot'], $json['nsockets']);
else if ($this->curTpl['class'] == ITEM_CLASS_GEM)
@@ -1914,10 +1989,10 @@ class ItemListFilter extends Filter
'gm' => [FILTER_V_LIST, [2, 3, 4], false], // gem rarity for weight calculation
'cr' => [FILTER_V_RANGE, [1, 177], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C};:]/ui', true ], // criteria values - only printable chars, no delimiters
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
'upg' => [FILTER_V_RANGE, [1, 999999], true ], // upgrade item ids
'gb' => [FILTER_V_LIST, [0, 1, 2, 3], false], // search result grouping
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ub' => [FILTER_V_LIST, [[1, 9], 11], false], // usable by classId
'qu' => [FILTER_V_RANGE, [0, 7], true ], // quality ids
@@ -2199,11 +2274,24 @@ class ItemListFilter extends Filter
protected function cbHasRandEnchant($cr)
{
$randIds = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, ABS(id) FROM ?_itemrandomenchant WHERE name_loc?d LIKE ?', User::$localeId, '%'.$cr[2].'%');
$tplIds = $randIds ? DB::World()->select('SELECT entry, ench FROM item_enchantment_template WHERE ench IN (?a)', $randIds) : [];
foreach ($tplIds as $k => &$set)
if (array_search($set['ench'], $randIds) < 0)
$randIds = DB::Aowow()->select('SELECT id AS ARRAY_KEY, ABS(id) AS `id`, name_loc?d, name_loc0 FROM ?_itemrandomenchant WHERE name_loc?d LIKE ?', User::$localeId, User::$localeId, '%'.$cr[2].'%');
$tplIds = $randIds ? DB::World()->select('SELECT `entry`, `ench` FROM item_enchantment_template WHERE `ench` IN (?a)', array_column($randIds, 'id')) : [];
foreach ($tplIds as &$set)
{
$z = array_column($randIds, 'id');
$x = array_search($set['ench'], $z);
if (isset($randIds[-$z[$x]]))
{
$set['entry'] *= -1;
$set['ench'] *= -1;
}
$set['name'] = Util::localizedString($randIds[$set['ench']], 'name', true);
}
// only enhance search results if enchantment by name is unique (implies only one enchantment per item is availabel)
if (count(array_unique(array_column($randIds, 'name_loc0'))) == 1)
$this->extraOpts['relEnchant'] = $tplIds;
if ($tplIds)
return ['randomEnchant', array_column($tplIds, 'entry')];
@@ -2218,8 +2306,10 @@ class ItemListFilter extends Filter
$this->formData['extraCols'][] = $cr[0];
$costs = DB::Aowow()->selectCol('SELECT id FROM ?_itemextendedcost WHERE reqPersonalrating '.$cr[1].' '.$cr[2]);
$items = [0];
if ($costs = DB::Aowow()->selectCol('SELECT id FROM ?_itemextendedcost WHERE reqPersonalrating '.$cr[1].' '.$cr[2]))
$items = DB::World()->selectCol($this->extCostQuery, $costs, $costs);
return ['id', $items];
}
@@ -2315,7 +2405,7 @@ class ItemListFilter extends Filter
{
// todo: do something sensible..
// // todo (med): get the avgbuyout into the listview
// if ($_ = DB::Characters()->select('SELECT ii.itemEntry AS ARRAY_KEY, AVG(ah.buyoutprice / ii.count) AS buyout FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid GROUP BY ii.itemEntry HAVING avgbuyout '.$cr[1].' ?f', $c[1]))
// if ($_ = DB::Characters()->select('SELECT ii.itemEntry AS ARRAY_KEY, AVG(ah.buyoutprice / ii.count) AS buyout FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid GROUP BY ii.itemEntry HAVING buyout '.$cr[1].' ?f', $c[1]))
// return ['i.id', array_keys($_)];
// else
// return [0];
@@ -2430,7 +2520,7 @@ class ItemListFilter extends Filter
case 1: // Yes
case 5: // No
$w = 1;
return;
break;
case 2: // Alliance
$w = 'reqRaceMask & '.RACE_MASK_ALLIANCE.' AND (reqRaceMask & '.RACE_MASK_HORDE.') = 0';
break;

View File

@@ -8,7 +8,7 @@ class ItemsetList extends BaseType
{
use ListviewHelper;
public static $type = TYPE_ITEMSET;
public static $type = Type::ITEMSET;
public static $brickFile = 'itemset';
public static $dataTable = '?_itemset';
@@ -80,14 +80,14 @@ class ItemsetList extends BaseType
$data = [];
if ($this->classes && ($addMask & GLOBALINFO_RELATED))
$data[TYPE_CLASS] = array_combine($this->classes, $this->classes);
$data[Type::CHR_CLASS] = array_combine($this->classes, $this->classes);
if ($this->pieceToSet && ($addMask & GLOBALINFO_SELF))
$data[TYPE_ITEM] = array_combine(array_keys($this->pieceToSet), array_keys($this->pieceToSet));
$data[Type::ITEM] = array_combine(array_keys($this->pieceToSet), array_keys($this->pieceToSet));
if ($addMask & GLOBALINFO_SELF)
foreach ($this->iterate() as $id => $__)
$data[TYPE_ITEMSET][$id] = ['name' => $this->getField('name', true)];
$data[Type::ITEMSET][$id] = ['name' => $this->getField('name', true)];
return $data;
}
@@ -98,19 +98,21 @@ class ItemsetList extends BaseType
return array();
$x = '<table><tr><td>';
$x .= '<span class="q'.$this->getField('quality').'">'.Util::jsEscape($this->getField('name', true)).'</span><br />';
$x .= '<span class="q'.$this->getField('quality').'">'.$this->getField('name', true).'</span><br />';
$nClasses = 0;
$nCl = 0;
if ($_ = $this->getField('classMask'))
{
$cl = Lang::getClassString($_, $__, $nClasses);
$x .= Util::ucFirst($nClasses > 1 ? Lang::game('classes') : Lang::game('class')).Lang::main('colon').$cl.'<br />';
$jsg = [];
$cl = Lang::getClassString($_, $jsg);
$nCl = count($jsg);
$x .= Util::ucFirst($nCl > 1 ? Lang::game('classes') : Lang::game('class')).Lang::main('colon').$cl.'<br />';
}
if ($_ = $this->getField('contentGroup'))
$x .= Util::jsEscape(Lang::itemset('notes', $_)).($this->getField('heroic') ? ' <i class="q2">('.Lang::item('heroic').')</i>' : '').'<br />';
$x .= Lang::itemset('notes', $_).($this->getField('heroic') ? ' <i class="q2">('.Lang::item('heroic').')</i>' : '').'<br />';
if (!$nClasses || !$this->getField('contentGroup'))
if (!$nCl || !$this->getField('contentGroup'))
$x.= Lang::itemset('types', $this->getField('type')).'<br />';
if ($bonuses = $this->getBonuses())
@@ -118,7 +120,7 @@ class ItemsetList extends BaseType
$x .= '<span>';
foreach ($bonuses as $b)
$x .= '<br /><span class=\"q13\">'.$b['bonus'].' '.Lang::itemset('_pieces').Lang::main('colon').'</span>'.Util::jsEscape($b['desc']);
$x .= '<br /><span class="q13">'.$b['bonus'].' '.Lang::itemset('_pieces').Lang::main('colon').'</span>'.$b['desc'];
$x .= '</span>';
}
@@ -186,8 +188,8 @@ class ItemsetListFilter extends Filter
protected $inputFields = array(
'cr' => [FILTER_V_RANGE, [2, 12], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 424]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C};:]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name / description - only printable chars, no delimiter
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / description - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'qu' => [FILTER_V_RANGE, [0, 7], true ], // quality
'ty' => [FILTER_V_RANGE, [1, 12], true ], // set type

View File

@@ -0,0 +1,74 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
class MailList extends BaseType
{
public static $type = Type::MAIL;
public static $brickFile = 'mail';
public static $dataTable = '?_mails';
protected $queryBase = 'SELECT m.*, m.id AS ARRAY_KEY FROM ?_mails m';
protected $queryOpts = [];
public function __construct($conditions = [])
{
parent::__construct($conditions);
if ($this->error)
return;
// post processing
foreach ($this->iterate() as $_id => &$_curTpl)
{
$_curTpl['name'] = Util::localizedString($_curTpl, 'subject', true);
if (!$_curTpl['name'])
{
$_curTpl['name'] = sprintf(Lang::mail('untitled'), $_id);
$_curTpl['subject_loc0'] = $_curTpl['name'];
}
}
}
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT subject_loc0, subject_loc2, subject_loc3, subject_loc4, subject_loc6, subject_loc8 FROM ?_mails WHERE id = ?d', $id);
return Util::localizedString($n, 'subject');
}
public function getListviewData()
{
$data = [];
foreach ($this->iterate() as $__)
{
$body = str_replace('[br]', ' ', Util::parseHtmlText($this->getField('text', true), true));
$data[$this->id] = array(
'id' => $this->id,
'subject' => $this->getField('subject', true),
'body' => Lang::trimTextClean($body),
'attachments' => [$this->getField('attachment')]
);
}
return $data;
}
public function getJSGlobals($addMask = 0)
{
$data = [];
foreach ($this->iterate() as $__)
if ($a = $this->curTpl['attachment'])
$data[Type::ITEM][$a] = $a;
return $data;
}
public function renderTooltip() { }
}
?>

View File

@@ -8,11 +8,11 @@ class PetList extends BaseType
{
use ListviewHelper;
public static $type = TYPE_PET;
public static $type = Type::PET;
public static $brickFile = 'pet';
public static $dataTable = '?_pet';
protected $queryBase = 'SELECT *, p.id AS ARRAY_KEY FROM ?_pet p';
protected $queryBase = 'SELECT p.*, p.id AS ARRAY_KEY FROM ?_pet p';
protected $queryOpts = array(
'p' => [['ic']],
'ic' => ['j' => ['?_icons ic ON p.iconId = ic.id', true], 's' => ', ic.name AS iconString'],
@@ -59,10 +59,10 @@ class PetList extends BaseType
if ($addMask & GLOBALINFO_RELATED)
for ($i = 1; $i <= 4; $i++)
if ($this->curTpl['spellId'.$i] > 0)
$data[TYPE_SPELL][$this->curTpl['spellId'.$i]] = $this->curTpl['spellId'.$i];
$data[Type::SPELL][$this->curTpl['spellId'.$i]] = $this->curTpl['spellId'.$i];
if ($addMask & GLOBALINFO_SELF)
$data[TYPE_PET][$this->id] = ['icon' => $this->curTpl['iconString']];
$data[Type::PET][$this->id] = ['icon' => $this->curTpl['iconString']];
}
return $data;

View File

@@ -13,7 +13,7 @@ class ProfileList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
{
if ($this->getField('user') && User::$id != $this->getField('user') && !($this->getField('cuFlags') & PROFILER_CU_PUBLISHED))
if (!$this->isVisibleToUser())
continue;
if (($addInfo & PROFILEINFO_PROFILE) && !$this->isCustom())
@@ -35,20 +35,20 @@ class ProfileList extends BaseType
'talenttree3' => $this->getField('talenttree3'),
'talentspec' => $this->getField('activespec') + 1, // 0 => 1; 1 => 2
'achievementpoints' => $this->getField('achievementpoints'),
'guild' => '$"'.$this->getField('guildname').'"', // force this to be a string
'guild' => '$"'.str_replace ('"', '', $this->curTpl['guildname']).'"',// force this to be a string
'guildrank' => $this->getField('guildrank'),
'realm' => Profiler::urlize($this->getField('realmName')),
'realm' => Profiler::urlize($this->getField('realmName'), true),
'realmname' => $this->getField('realmName'),
// 'battlegroup' => Profiler::urlize($this->getField('battlegroup')), // was renamed to subregion somewhere around cata release
// 'battlegroupname' => $this->getField('battlegroup'),
'gearscore' => $this->getField('gearscore')
);
if ($addInfo & PROFILEINFO_USER)
$data[$this->id]['published'] = (int)!!($this->getField('cuFlags') & PROFILER_CU_PUBLISHED);
// for the lv this determins if the link is profile=<id> or profile=<region>.<realm>.<name>
if ($this->isCustom())
$data[$this->id]['published'] = (int)!!($this->getField('cuFlags') & PROFILER_CU_PUBLISHED);
else
if (!$this->isCustom())
$data[$this->id]['region'] = Profiler::urlize($this->getField('region'));
if ($addInfo & PROFILEINFO_ARENA)
@@ -64,12 +64,17 @@ class ProfileList extends BaseType
$data[$this->id][$col] = $this->getField($col);
if ($addInfo & PROFILEINFO_PROFILE)
{
if ($_ = $this->getField('description'))
$data[$this->id]['description'] = $_;
if ($addInfo & PROFILEINFO_PROFILE)
if ($_ = $this->getField('icon'))
$data[$this->id]['icon'] = $_;
}
if ($addInfo & PROFILEINFO_CHARACTER)
if ($_ = $this->getField('renameItr'))
$data[$this->id]['renameItr'] = $_;
if ($this->getField('cuFlags') & PROFILER_CU_PINNED)
$data[$this->id]['pinned'] = 1;
@@ -81,18 +86,18 @@ class ProfileList extends BaseType
return array_values($data);
}
public function renderTooltip($interactive = false)
public function renderTooltip()
{
if (!$this->curTpl)
return [];
$title = '';
$name = $this->getField('name');
if ($_ = $this->getField('chosenTitle'))
$title = (new TitleList(array(['bitIdx', $_])))->getField($this->getField('gender') ? 'female' : 'male', true);
if ($_ = $this->getField('title'))
$title = (new TitleList(array(['id', $_])))->getField($this->getField('gender') ? 'female' : 'male', true);
if ($this->isCustom())
$name .= ' (Custom Profile)';
$name .= Lang::profiler('customProfile');
else if ($title)
$name = sprintf($title, $name);
@@ -115,7 +120,7 @@ class ProfileList extends BaseType
foreach ($this->iterate() as $id => $__)
{
if (($addMask & PROFILEINFO_PROFILE) && ($this->getField('cuFlags') & PROFILER_CU_PROFILE))
if (($addMask & PROFILEINFO_PROFILE) && $this->isCustom())
{
$profile = array(
'id' => $this->getField('id'),
@@ -134,7 +139,7 @@ class ProfileList extends BaseType
continue;
}
if ($addMask & PROFILEINFO_CHARACTER && !($this->getField('cuFlags') & PROFILER_CU_PROFILE))
if ($addMask & PROFILEINFO_CHARACTER && !$this->isCustom())
{
if (!isset($realms[$this->getField('realm')]))
continue;
@@ -161,6 +166,70 @@ class ProfileList extends BaseType
{
return $this->getField('cuFlags') & PROFILER_CU_PROFILE;
}
public function isVisibleToUser()
{
if (!$this->isCustom() || User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
return true;
if ($this->getField('cuFlags') & PROFILER_CU_DELETED)
return false;
if (User::$id == $this->getField('user'))
return true;
return (bool)($this->getField('cuFlags') & PROFILER_CU_PUBLISHED);
}
public function getIcon()
{
if ($_ = $this->getField('icon'))
return $_;
$str = 'chr_';
switch ($this->getField('race'))
{
case 1: $str .= 'human_'; break;
case 2: $str .= 'orc_'; break;
case 3: $str .= 'dwarf_'; break;
case 4: $str .= 'nightelf_'; break;
case 5: $str .= 'scourge_'; break;
case 6: $str .= 'tauren_'; break;
case 7: $str .= 'gnome_'; break;
case 8: $str .= 'troll_'; break;
case 10: $str .= 'bloodelf_'; break;
case 11: $str .= 'draenei_'; break;
}
switch ($this->getField('gender'))
{
case 0: $str .= 'male_'; break;
case 1: $str .= 'female_'; break;
}
switch ($this->getField('class'))
{
case 1: $str .= 'warrior0'; break;
case 2: $str .= 'paladin0'; break;
case 3: $str .= 'hunter0'; break;
case 4: $str .= 'rogue0'; break;
case 5: $str .= 'priest0'; break;
case 6: $str .= 'deathknight0'; break;
case 7: $str .= 'shaman0'; break;
case 8: $str .= 'mage0'; break;
case 9: $str .= 'warlock0'; break;
case 11: $str .= 'druid0'; break;
}
$level = $this->getField('level');
if ($level > 59)
$str .= floor(($level - 60) / 10) + 2;
else
$str .= 1;
return $str;
}
}
@@ -182,7 +251,7 @@ class ProfileListFilter extends Filter
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
2 => [FILTER_CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num]
3 => [FILTER_CR_NUMERIC, 'achievementpoints', NUM_CAST_INT ], // achievementpoints [num]
3 => [FILTER_CR_CALLBACK, 'cbAchievs', null, null], // achievementpoints [num]
5 => [FILTER_CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num]
6 => [FILTER_CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num]
7 => [FILTER_CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num]
@@ -199,17 +268,17 @@ class ProfileListFilter extends Filter
20 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib5v5 [num]
21 => [FILTER_CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str]
23 => [FILTER_CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement
25 => [FILTER_CR_CALLBACK, 'cbProfession', 171, null], // alchemy [num]
26 => [FILTER_CR_CALLBACK, 'cbProfession', 164, null], // blacksmithing [num]
27 => [FILTER_CR_CALLBACK, 'cbProfession', 333, null], // enchanting [num]
28 => [FILTER_CR_CALLBACK, 'cbProfession', 202, null], // engineering [num]
29 => [FILTER_CR_CALLBACK, 'cbProfession', 182, null], // herbalism [num]
30 => [FILTER_CR_CALLBACK, 'cbProfession', 773, null], // inscription [num]
31 => [FILTER_CR_CALLBACK, 'cbProfession', 755, null], // jewelcrafting [num]
32 => [FILTER_CR_CALLBACK, 'cbProfession', 165, null], // leatherworking [num]
33 => [FILTER_CR_CALLBACK, 'cbProfession', 186, null], // mining [num]
34 => [FILTER_CR_CALLBACK, 'cbProfession', 393, null], // skinning [num]
35 => [FILTER_CR_CALLBACK, 'cbProfession', 197, null], // tailoring [num]
25 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ALCHEMY, null], // alchemy [num]
26 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_BLACKSMITHING, null], // blacksmithing [num]
27 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ENCHANTING, null], // enchanting [num]
28 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ENGINEERING, null], // engineering [num]
29 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_HERBALISM, null], // herbalism [num]
30 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_INSCRIPTION, null], // inscription [num]
31 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_JEWELCRAFTING, null], // jewelcrafting [num]
32 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_LEATHERWORKING, null], // leatherworking [num]
33 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_MINING, null], // mining [num]
34 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_SKINNING, null], // skinning [num]
35 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_TAILORING, null], // tailoring [num]
36 => [FILTER_CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn]
);
@@ -218,8 +287,8 @@ class ProfileListFilter extends Filter
protected $inputFields = array(
'cr' => [FILTER_V_RANGE, [1, 36], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C};]/ui', true ], // criteria values
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'crv' => [FILTER_V_REGEX, '/[\p{C}:;%\\\\]/ui', true ], // criteria values
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
'si' => [FILTER_V_LIST, [1, 2], false], // side
@@ -246,7 +315,7 @@ class ProfileListFilter extends Filter
parent::__construct($fromPOST, $opts);
if (!empty($this->fiData['c']['cr']))
if (array_intersect($this->fiData['c']['cr'], [2, 3, 5, 6, 7, 21]))
if (array_intersect($this->fiData['c']['cr'], [2, 5, 6, 7, 21]))
$this->useLocalList = true;
}
@@ -274,8 +343,8 @@ class ProfileListFilter extends Filter
// name [str] - the table is case sensitive. Since i down't want to destroy indizes, lets alter the search terms
if (!empty($_v['na']))
{
$lower = $this->modularizeString([$k.'.name'], Util::lower($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on');
$proper = $this->modularizeString([$k.'.name'], Util::ucWords($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on');
$lower = $this->modularizeString([$k.'.name'], Util::lower($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on', true);
$proper = $this->modularizeString([$k.'.name'], Util::ucWords($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on', true);
$parts[] = ['OR', $lower, $proper];
}
@@ -310,7 +379,7 @@ class ProfileListFilter extends Filter
protected function cbRegionCheck(&$v)
{
if ($v == 'eu' || $v == 'us')
if (in_array($v, Util::$regions))
{
$this->parentCats[0] = $v; // directly redirect onto this region
$v = ''; // remove from filter
@@ -348,7 +417,7 @@ class ProfileListFilter extends Filter
if ($this->useLocalList)
{
$this->extraOpts[$k] = array(
'j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_SKILL.' AND '.$k.'.typeId = '.$skillId.' AND '.$k.'.cur '.$cr[1].' '.$cr[2], true],
'j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.Type::SKILL.' AND '.$k.'.typeId = '.$skillId.' AND '.$k.'.cur '.$cr[1].' '.$cr[2], true],
's' => [', '.$k.'.cur AS '.$col]
);
return [$k.'.typeId', null, '!'];
@@ -375,7 +444,7 @@ class ProfileListFilter extends Filter
if ($this->useLocalList)
{
$this->extraOpts[$k] = ['j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_ACHIEVEMENT.' AND '.$k.'.typeId = '.$cr[2], true]];
$this->extraOpts[$k] = ['j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.Type::ACHIEVEMENT.' AND '.$k.'.typeId = '.$cr[2], true]];
return [$k.'.typeId', null, '!'];
}
else
@@ -436,6 +505,17 @@ class ProfileListFilter extends Filter
return ['AND', ['at.type', $this->enums[-1][$cr[0]]], ['at.rating', $cr[2], $cr[1]]];
}
protected function cbAchievs($cr)
{
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
return false;
if ($this->useLocalList)
return ['p.achievementpoints', $cr[2], $cr[1]];
else
return ['cap.counter', $cr[2], $cr[1]];
}
}
@@ -443,9 +523,8 @@ class RemoteProfileList extends ProfileList
{
protected $queryBase = 'SELECT `c`.*, `c`.`guid` AS ARRAY_KEY FROM characters c';
protected $queryOpts = array(
'c' => [['gm', 'g', 'ca', 'ct'], 'g' => 'ARRAY_KEY', 'o' => 'level DESC, name ASC'],
'ca' => ['j' => ['character_achievement ca ON ca.guid = c.guid', true], 's' => ', GROUP_CONCAT(DISTINCT ca.achievement SEPARATOR " ") AS _acvs'],
'ct' => ['j' => ['character_talent ct ON ct.guid = c.guid AND ct.spec = c.activespec', true], 's' => ', GROUP_CONCAT(DISTINCT ct.spell SEPARATOR " ") AS _talents'],
'c' => [['gm', 'g', 'cap']], // 12698: use criteria of Achievement 4496 as shortcut to get total achievement points
'cap' => ['j' => ['character_achievement_progress cap ON cap.guid = c.guid AND cap.criteria = 12698', true], 's' => ', IFNULL(cap.counter, 0) AS achievementpoints'],
'gm' => ['j' => ['guild_member gm ON gm.guid = c.guid', true], 's' => ', gm.rank AS guildrank'],
'g' => ['j' => ['guild g ON g.guildid = gm.guildid', true], 's' => ', g.guildid AS guild, g.name AS guildname'],
'atm' => ['j' => ['arena_team_member atm ON atm.guid = c.guid', true], 's' => ', atm.personalRating AS rating'],
@@ -469,11 +548,9 @@ class RemoteProfileList extends ProfileList
reset($this->dbNames); // only use when querying single realm
$realmId = key($this->dbNames);
$realms = Profiler::getRealms();
$acvCache = [];
$talentCache = [];
$atCache = [];
$talentSpells = [];
$talentLookup = [];
$distrib = null;
$talentData = [];
$limit = CFG_SQL_LIMIT_DEFAULT;
foreach ($conditions as $c)
@@ -487,7 +564,7 @@ class RemoteProfileList extends ProfileList
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
// realm
$r = explode(':', $guid)[0];
[$r, $g] = explode(':', $guid);
if (!empty($realms[$r]))
{
$curTpl['realm'] = $r;
@@ -504,17 +581,10 @@ class RemoteProfileList extends ProfileList
// temp id
$curTpl['id'] = 0;
// achievement points pre
if ($acvs = explode(' ', $curTpl['_acvs']))
foreach ($acvs as $a)
if ($a && !isset($acvCache[$a]))
$acvCache[$a] = $a;
// talent points pre
if ($talents = explode(' ', $curTpl['_talents']))
foreach ($talents as $t)
if ($t && !isset($talentCache[$t]))
$talentCache[$t] = $t;
$talentLookup[$r][$g] = [];
$talentSpells[] = $curTpl['class'];
$curTpl['activespec'] = $curTpl['activeTalentGroup'];
// equalize distribution
if ($limit != CFG_SQL_LIMIT_NONE)
@@ -525,11 +595,29 @@ class RemoteProfileList extends ProfileList
$distrib[$curTpl['realm']]++;
}
// char is pending rename
if ($curTpl['at_login'] & 0x1)
{
if (!isset($this->rnItr[$curTpl['name']]))
$this->rnItr[$curTpl['name']] = DB::Aowow()->selectCell('SELECT MAX(renameItr) FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID IS NOT NULL AND name = ?', $r, $curTpl['name']) ?: 0;
// already saved as "pending rename"
if ($rnItr = DB::Aowow()->selectCell('SELECT renameItr FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $r, $g))
$curTpl['renameItr'] = $rnItr;
// not yet recognized: get max itr
else
$curTpl['renameItr'] = ++$this->rnItr[$curTpl['name']];
}
else
$curTpl['renameItr'] = 0;
$curTpl['cuFlags'] = 0;
}
if ($talentCache)
$talentData = DB::Aowow()->select('SELECT spell AS ARRAY_KEY, tab, rank FROM ?_talents WHERE spell IN (?a)', $talentCache);
foreach ($talentLookup as $realm => $chars)
$talentLookup[$realm] = DB::Characters($realm)->selectCol('SELECT guid AS ARRAY_KEY, spell AS ARRAY_KEY2, talentGroup FROM character_talent ct WHERE guid IN (?a)', array_keys($chars));
$talentSpells = DB::Aowow()->select('SELECT spell AS ARRAY_KEY, tab, `rank` FROM ?_talents WHERE class IN (?a)', array_unique($talentSpells));
if ($distrib !== null)
{
@@ -538,9 +626,6 @@ class RemoteProfileList extends ProfileList
$d = ceil($limit * $d / $total);
}
if ($acvCache)
$acvCache = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, points FROM ?_achievement WHERE id IN (?a)', $acvCache);
foreach ($this->iterate() as $guid => &$curTpl)
{
if ($distrib !== null)
@@ -555,24 +640,20 @@ class RemoteProfileList extends ProfileList
$limit--;
}
$a = explode(' ', $curTpl['_acvs']);
$t = explode(' ', $curTpl['_talents']);
unset($curTpl['_acvs']);
unset($curTpl['_talents']);
// achievement points post
$curTpl['achievementpoints'] = array_sum(array_intersect_key($acvCache, array_combine($a, $a)));
[$r, $g] = explode(':', $guid);
// talent points post
$curTpl['talenttree1'] = 0;
$curTpl['talenttree2'] = 0;
$curTpl['talenttree3'] = 0;
foreach ($talentData as $spell => $data)
if (in_array($spell, $t))
if (!empty($talentLookup[$r][$g]))
{
$talents = array_filter($talentLookup[$r][$g], function($v) use ($curTpl) { return $curTpl['activespec'] == $v; } );
foreach (array_intersect_key($talentSpells, $talents) as $spell => $data)
$curTpl['talenttree'.($data['tab'] + 1)] += $data['rank'];
}
}
}
public function getListviewData($addInfoMask = 0, array $reqCols = [])
{
@@ -594,6 +675,7 @@ class RemoteProfileList extends ProfileList
'realm' => $this->getField('realm'),
'realmGUID' => $this->getField('guid'),
'name' => $this->getField('name'),
'renameItr' => $this->getField('renameItr'),
'race' => $this->getField('race'),
'class' => $this->getField('class'),
'level' => $this->getField('level'),
@@ -633,7 +715,7 @@ class RemoteProfileList extends ProfileList
if ($baseData)
{
foreach (Util::createSqlBatchInsert($baseData) as $ins)
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_profiles (?#) VALUES '.$ins, array_keys(reset($baseData)));
DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE name = VALUES(name), renameItr = VALUES(renameItr)', array_keys(reset($baseData)));
// merge back local ids
$localIds = DB::Aowow()->select(
@@ -672,7 +754,7 @@ class LocalProfileList extends ProfileList
$realms = Profiler::getRealms();
// post processing
$acvPoints = DB::Aowow()->selectCol('SELECT pc.id AS ARRAY_KEY, SUM(a.points) FROM ?_profiler_completion pc LEFT JOIN ?_achievement a ON a.id = pc.typeId WHERE pc.`type` = ?d AND pc.id IN (?a) GROUP BY pc.id', TYPE_ACHIEVEMENT, $this->getFoundIDs());
$acvPoints = DB::Aowow()->selectCol('SELECT pc.id AS ARRAY_KEY, SUM(a.points) FROM ?_profiler_completion pc LEFT JOIN ?_achievement a ON a.id = pc.typeId WHERE pc.`type` = ?d AND pc.id IN (?a) GROUP BY pc.id', Type::ACHIEVEMENT, $this->getFoundIDs());
foreach ($this->iterate() as $id => &$curTpl)
{

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class QuestList extends BaseType
{
public static $type = TYPE_QUEST;
public static $type = Type::QUEST;
public static $brickFile = 'quest';
public static $dataTable = '?_quests';
@@ -49,18 +49,18 @@ class QuestList extends BaseType
for ($i = 1; $i < 7; $i++)
{
if ($_ = $_curTpl['reqItemId'.$i])
$requires[TYPE_ITEM][] = $_;
$requires[Type::ITEM][] = $_;
if ($i > 4)
continue;
if ($_curTpl['reqNpcOrGo'.$i] > 0)
$requires[TYPE_NPC][] = $_curTpl['reqNpcOrGo'.$i];
$requires[Type::NPC][] = $_curTpl['reqNpcOrGo'.$i];
else if ($_curTpl['reqNpcOrGo'.$i] < 0)
$requires[TYPE_OBJECT][] = -$_curTpl['reqNpcOrGo'.$i];
$requires[Type::OBJECT][] = -$_curTpl['reqNpcOrGo'.$i];
if ($_ = $_curTpl['reqSourceItemId'.$i])
$requires[TYPE_ITEM][] = $_;
$requires[Type::ITEM][] = $_;
}
if ($requires)
$this->requires[$id] = $requires;
@@ -70,24 +70,24 @@ class QuestList extends BaseType
$choices = [];
if ($_ = $_curTpl['rewardTitleId'])
$rewards[TYPE_TITLE][] = $_;
$rewards[Type::TITLE][] = $_;
if ($_ = $_curTpl['rewardHonorPoints'])
$rewards[TYPE_CURRENCY][104] = $_;
$rewards[Type::CURRENCY][104] = $_;
if ($_ = $_curTpl['rewardArenaPoints'])
$rewards[TYPE_CURRENCY][103] = $_;
$rewards[Type::CURRENCY][103] = $_;
for ($i = 1; $i < 7; $i++)
{
if ($_ = $_curTpl['rewardChoiceItemId'.$i])
$choices[TYPE_ITEM][$_] = $_curTpl['rewardChoiceItemCount'.$i];
$choices[Type::ITEM][$_] = $_curTpl['rewardChoiceItemCount'.$i];
if ($i > 5)
continue;
if ($_ = $_curTpl['rewardFactionId'.$i])
$rewards[TYPE_FACTION][$_] = $_curTpl['rewardFactionValue'.$i];
$rewards[Type::FACTION][$_] = $_curTpl['rewardFactionValue'.$i];
if ($i > 4)
continue;
@@ -96,9 +96,9 @@ class QuestList extends BaseType
{
$qty = $_curTpl['rewardItemCount'.$i];
if (in_array($_, $currencies))
$rewards[TYPE_CURRENCY][array_search($_, $currencies)] = $qty;
$rewards[Type::CURRENCY][array_search($_, $currencies)] = $qty;
else
$rewards[TYPE_ITEM][$_] = $qty;
$rewards[Type::ITEM][$_] = $qty;
}
}
if ($rewards)
@@ -112,7 +112,7 @@ class QuestList extends BaseType
// static use START
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_quests WHERE id = ?d', $id);
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_quests WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
// static use END
@@ -156,7 +156,7 @@ class QuestList extends BaseType
{
$data[$this->id] = array(
"n" => $this->getField('name', true),
"t" => TYPE_QUEST,
"t" => Type::QUEST,
"ti" => $this->id,
"c" => $this->curTpl['cat1'],
"c2" => $this->curTpl['cat2']
@@ -175,7 +175,7 @@ class QuestList extends BaseType
if (!(Game::sideByRaceMask($this->curTpl['reqRaceMask']) & $side))
continue;
list($series, $first) = DB::Aowow()->SelectRow(
[$series, $first] = DB::Aowow()->SelectRow(
'SELECT IF(prev.id OR cur.nextQuestIdChain, 1, 0) AS "0", IF(prev.id IS NULL AND cur.nextQuestIdChain, 1, 0) AS "1" FROM ?_quests cur LEFT JOIN ?_quests prev ON prev.nextQuestIdChain = cur.id WHERE cur.id = ?d',
$this->id
);
@@ -214,16 +214,16 @@ class QuestList extends BaseType
'xp' => $this->curTpl['rewardXP']
);
if (!empty($this->rewards[$this->id][TYPE_CURRENCY]))
foreach ($this->rewards[$this->id][TYPE_CURRENCY] as $iId => $qty)
if (!empty($this->rewards[$this->id][Type::CURRENCY]))
foreach ($this->rewards[$this->id][Type::CURRENCY] as $iId => $qty)
$data[$this->id]['currencyrewards'][] = [$iId, $qty];
if (!empty($this->rewards[$this->id][TYPE_ITEM]))
foreach ($this->rewards[$this->id][TYPE_ITEM] as $iId => $qty)
if (!empty($this->rewards[$this->id][Type::ITEM]))
foreach ($this->rewards[$this->id][Type::ITEM] as $iId => $qty)
$data[$this->id]['itemrewards'][] = [$iId, $qty];
if (!empty($this->choices[$this->id][TYPE_ITEM]))
foreach ($this->choices[$this->id][TYPE_ITEM] as $iId => $qty)
if (!empty($this->choices[$this->id][Type::ITEM]))
foreach ($this->choices[$this->id][Type::ITEM] as $iId => $qty)
$data[$this->id]['itemchoices'][] = [$iId, $qty];
if ($_ = $this->curTpl['rewardTitleId'])
@@ -313,7 +313,7 @@ class QuestList extends BaseType
if (!$this->curTpl)
return null;
$title = Util::jsEscape($this->getField('name', true));
$title = htmlentities($this->getField('name', true));
$level = $this->curTpl['level'];
if ($level < 0)
$level = 0;
@@ -332,7 +332,7 @@ class QuestList extends BaseType
$x .= '<table><tr><td><b class="q">'.$title.'</b></td></tr></table>';
$x .= '<table><tr><td><br />'.$this->parseText('objectives');
$x .= '<table><tr><td><br />'.$this->parseText('objectives', false);
$xReq = '';
@@ -350,7 +350,7 @@ class QuestList extends BaseType
else
$name = $rng > 0 ? CreatureList::getName($rng) : GameObjectList::getName(-$rng);
$xReq .= '<br /> - '.Util::jsEscape($name).($rngQty > 1 ? ' x '.$rngQty : null);
$xReq .= '<br /> - '.$name.($rngQty > 1 ? ' x '.$rngQty : null);
}
for ($i = 1; $i < 7; $i++)
@@ -361,11 +361,11 @@ class QuestList extends BaseType
if (!$ri || $riQty < 1)
continue;
$xReq .= '<br /> - '.Util::jsEscape(ItemList::getName($ri)).($riQty > 1 ? ' x '.$riQty : null);
$xReq .= '<br /> - '.ItemList::getName($ri).($riQty > 1 ? ' x '.$riQty : null);
}
if ($et = $this->getField('end', true))
$xReq .= '<br /> - '.Util::jsEscape($et);
$xReq .= '<br /> - '.$et;
if ($_ = $this->getField('rewardOrReqMoney'))
if ($_ < 0)
@@ -390,31 +390,31 @@ class QuestList extends BaseType
// items
for ($i = 1; $i < 5; $i++)
if ($this->curTpl['rewardItemId'.$i] > 0)
$data[TYPE_ITEM][$this->curTpl['rewardItemId'.$i]] = $this->curTpl['rewardItemId'.$i];
$data[Type::ITEM][$this->curTpl['rewardItemId'.$i]] = $this->curTpl['rewardItemId'.$i];
for ($i = 1; $i < 7; $i++)
if ($this->curTpl['rewardChoiceItemId'.$i] > 0)
$data[TYPE_ITEM][$this->curTpl['rewardChoiceItemId'.$i]] = $this->curTpl['rewardChoiceItemId'.$i];
$data[Type::ITEM][$this->curTpl['rewardChoiceItemId'.$i]] = $this->curTpl['rewardChoiceItemId'.$i];
// spells
if ($this->curTpl['rewardSpell'] > 0)
$data[TYPE_SPELL][$this->curTpl['rewardSpell']] = $this->curTpl['rewardSpell'];
$data[Type::SPELL][$this->curTpl['rewardSpell']] = $this->curTpl['rewardSpell'];
if ($this->curTpl['rewardSpellCast'] > 0)
$data[TYPE_SPELL][$this->curTpl['rewardSpellCast']] = $this->curTpl['rewardSpellCast'];
$data[Type::SPELL][$this->curTpl['rewardSpellCast']] = $this->curTpl['rewardSpellCast'];
// titles
if ($this->curTpl['rewardTitleId'] > 0)
$data[TYPE_TITLE][$this->curTpl['rewardTitleId']] = $this->curTpl['rewardTitleId'];
$data[Type::TITLE][$this->curTpl['rewardTitleId']] = $this->curTpl['rewardTitleId'];
// currencies
if (!empty($this->rewards[$this->id][TYPE_CURRENCY]))
foreach ($this->rewards[$this->id][TYPE_CURRENCY] as $id => $__)
$data[TYPE_CURRENCY][$id] = $id;
if (!empty($this->rewards[$this->id][Type::CURRENCY]))
foreach ($this->rewards[$this->id][Type::CURRENCY] as $id => $__)
$data[Type::CURRENCY][$id] = $id;
}
if ($addMask & GLOBALINFO_SELF)
$data[TYPE_QUEST][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::QUEST][$this->id] = ['name' => $this->getField('name', true)];
}
return $data;
@@ -457,7 +457,7 @@ class QuestListFilter extends Filter
34 => [FILTER_CR_CALLBACK, 'cbAvailable', null, null], // availabletoplayers [yn]
36 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
37 => [FILTER_CR_CALLBACK, 'cbClassSpec', null, null], // classspecific [enum]
37 => [FILTER_CR_CALLBACK, 'cbRaceSpec', null, null], // racespecific [enum]
38 => [FILTER_CR_CALLBACK, 'cbRaceSpec', null, null], // racespecific [enum]
42 => [FILTER_CR_STAFFFLAG, 'flags' ], // flags
43 => [FILTER_CR_CALLBACK, 'cbCurrencyReward', null, null], // currencyrewarded [enum]
44 => [FILTER_CR_CALLBACK, 'cbLoremaster', null, null], // countsforloremaster_stc [yn]
@@ -469,7 +469,7 @@ class QuestListFilter extends Filter
'cr' => [FILTER_V_RANGE, [1, 45], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/\D/', true ], // criteria values - only numerals
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name / text - only printable chars, no delimiter
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / text - only printable chars, no delimiter
'ex' => [FILTER_V_EQUAL, 'on', false], // also match subname
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'minle' => [FILTER_V_RANGE, [1, 99], false], // min quest level
@@ -581,11 +581,11 @@ class QuestListFilter extends Filter
switch ($cr[1])
{
case 1: // npc
return ['AND', ['qse.type', TYPE_NPC], ['qse.method', $flags, '&']];
return ['AND', ['qse.type', Type::NPC], ['qse.method', $flags, '&']];
case 2: // object
return ['AND', ['qse.type', TYPE_OBJECT], ['qse.method', $flags, '&']];
return ['AND', ['qse.type', Type::OBJECT], ['qse.method', $flags, '&']];
case 3: // item
return ['AND', ['qse.type', TYPE_ITEM], ['qse.method', $flags, '&']];
return ['AND', ['qse.type', Type::ITEM], ['qse.method', $flags, '&']];
}
return false;

View File

@@ -6,11 +6,11 @@ if (!defined('AOWOW_REVISION'))
class SkillList extends BaseType
{
public static $type = TYPE_SKILL;
public static $type = Type::SKILL;
public static $brickFile = 'skill';
public static $dataTable = '?_skillline';
protected $queryBase = 'SELECT *, sl.id AS ARRAY_KEY FROM ?_skillline sl';
protected $queryBase = 'SELECT sl.*, sl.id AS ARRAY_KEY FROM ?_skillline sl';
protected $queryOpts = array(
'sl' => [['ic']],
'ic' => ['j' => ['?_icons ic ON ic.id = sl.iconId', true], 's' => ', ic.name AS iconString'],
@@ -40,7 +40,7 @@ class SkillList extends BaseType
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_skillline WHERE id = ?d', $id);
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_skillline WHERE id = ?d', $id);
return Util::localizedString($n, 'name');
}
@@ -54,11 +54,11 @@ class SkillList extends BaseType
'category' => $this->curTpl['typeCat'],
'categorybak' => $this->curTpl['categoryId'],
'id' => $this->id,
'name' => Util::jsEscape($this->getField('name', true)),
'name' => $this->getField('name', true),
'profession' => $this->curTpl['professionMask'],
'recipeSubclass' => $this->curTpl['recipeSubClass'],
'specializations' => Util::toJSON($this->curTpl['specializations'], JSON_NUMERIC_CHECK),
'icon' => Util::jsEscape($this->curTpl['iconString'])
'icon' => $this->curTpl['iconString']
);
}
@@ -70,7 +70,7 @@ class SkillList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[self::$type][$this->id] = ['name' => Util::jsEscape($this->getField('name', true)), 'icon' => Util::jsEscape($this->curTpl['iconString'])];
$data[self::$type][$this->id] = ['name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString']];
return $data;
}

View File

@@ -8,12 +8,12 @@ class SoundList extends BaseType
{
use spawnHelper;
public static $type = TYPE_SOUND;
public static $type = Type::SOUND;
public static $brickFile = 'sound';
public static $dataTable = '?_sounds';
public static $contribute = CONTRIBUTE_CO;
protected $queryBase = 'SELECT *, s.id AS ARRAY_KEY FROM ?_sounds s';
protected $queryBase = 'SELECT s.*, s.id AS ARRAY_KEY FROM ?_sounds s';
private $fileBuffer = [];
private static $fileTypes = array(
@@ -43,9 +43,11 @@ class SoundList extends BaseType
if ($this->fileBuffer)
{
$files = DB::Aowow()->select('SELECT id AS ARRAY_KEY, `id`, `file` AS title, `type` FROM ?_sounds_files sf WHERE id IN (?a)', array_keys($this->fileBuffer));
$files = DB::Aowow()->select('SELECT id AS ARRAY_KEY, `id`, `file` AS title, `type`, `path` FROM ?_sounds_files sf WHERE id IN (?a)', array_keys($this->fileBuffer));
foreach ($files as $id => $data)
{
// 3.3.5 bandaid - need fullpath to play via wow API, remove for cata and later
$data['path'] = str_replace('\\', '\\\\', $data['path'] ? $data['path'] . '\\' . $data['title'] : $data['title']);
// skipp file extension
$data['title'] = substr($data['title'], 0, -4);
// enum to string
@@ -58,13 +60,6 @@ class SoundList extends BaseType
}
}
public static function getName($id)
{
$this->getEntry($id);
return $this->getField('name');
}
public function getListviewData()
{
$data = [];
@@ -88,7 +83,7 @@ class SoundList extends BaseType
foreach ($this->iterate() as $__)
$data[self::$type][$this->id] = array(
'name' => Util::jsEscape($this->getField('name', true)),
'name' => $this->getField('name', true),
'type' => $this->getField('cat'),
'files' => array_values(array_filter($this->getField('files')))
);
@@ -101,6 +96,12 @@ class SoundList extends BaseType
class SoundListFilter extends Filter
{
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
'ty' => [FILTER_V_LIST, [[1, 4], 6, 9, 10, 12, 13, 14, 16, 17, [19, 23], [25, 31], 50, 52, 53], true ] // type
);
// we have no criteria for this one...
protected function createSQLForCriterium(&$cr)
{
@@ -109,12 +110,6 @@ class SoundListFilter extends Filter
return [1];
}
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
'ty' => [FILTER_V_LIST, [[1, 4], 6, 9, 10, 12, 13, 14, 16, 17, [19, 23], [25, 31], 50, 52, 53], true ] // type
);
protected function createSQLForValues()
{
$parts = [];

View File

@@ -12,7 +12,7 @@ class SpellList extends BaseType
public $relItems = null;
public $sources = [];
public static $type = TYPE_SPELL;
public static $type = Type::SPELL;
public static $brickFile = 'spell';
public static $dataTable = '?_spell';
@@ -31,7 +31,7 @@ class SpellList extends BaseType
);
public static $effects = array(
'heal' => [ 0,/*3,*/10, 67, 75, 136 ], // <no effect>, Dummy, Heal, Heal Max Health, Heal Mechanical, Heal Percent
'heal' => [ 0,/*3,*/10, 67, 75, 136 ], // <no effect>, /*Dummy*/, Heal, Heal Max Health, Heal Mechanical, Heal Percent
'damage' => [ 0, 2, 3, 9, 62 ], // <no effect>, Dummy, School Damage, Health Leech, Power Burn
'itemCreate' => [24, 34, 59, 66, 157 ], // createItem, changeItem, randomItem, createManaGem, createItem2
'trigger' => [ 3, 32, 64, 101, 142, 148, 151, 152, 155, 160, 164], // dummy, trigger missile, trigger spell, feed pet, force cast, force cast with value, unk, trigger spell 2, unk, dualwield 2H, unk, remove aura
@@ -54,10 +54,10 @@ class SpellList extends BaseType
protected $queryBase = 'SELECT s.*, s.id AS ARRAY_KEY FROM ?_spell s';
protected $queryOpts = array(
's' => [['src', 'sr', 'ic', 'ica']], // 6: TYPE_SPELL
's' => [['src', 'sr', 'ic', 'ica']], // 6: Type::SPELL
'ic' => ['j' => ['?_icons ic ON ic.id = s.iconId', true], 's' => ', ic.name AS iconString'],
'ica' => ['j' => ['?_icons ica ON ica.id = s.iconIdAlt', true], 's' => ', ica.name AS iconStringAlt'],
'sr' => ['j' => ['?_spellrange sr ON sr.id = s.rangeId'], 's' => ', sr.rangeMinHostile, sr.rangeMinFriend, sr.rangeMaxHostile, sr.rangeMaxFriend, sr.name_loc0 AS rangeText_loc0, sr.name_loc2 AS rangeText_loc2, sr.name_loc3 AS rangeText_loc3, sr.name_loc6 AS rangeText_loc6, sr.name_loc8 AS rangeText_loc8'],
'sr' => ['j' => ['?_spellrange sr ON sr.id = s.rangeId'], 's' => ', sr.rangeMinHostile, sr.rangeMinFriend, sr.rangeMaxHostile, sr.rangeMaxFriend, sr.name_loc0 AS rangeText_loc0, sr.name_loc2 AS rangeText_loc2, sr.name_loc3 AS rangeText_loc3, sr.name_loc4 AS rangeText_loc4, sr.name_loc6 AS rangeText_loc6, sr.name_loc8 AS rangeText_loc8'],
'src' => ['j' => ['?_source src ON type = 6 AND typeId = s.id', true], 's' => ', src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24']
);
@@ -138,7 +138,7 @@ class SpellList extends BaseType
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_spell WHERE id = ?d', $id );
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_spell WHERE id = ?d', $id );
return Util::localizedString($n, 'name');
}
// end static use
@@ -161,7 +161,7 @@ class SpellList extends BaseType
// Enchant Item Permanent (53) / Temporary (54)
if (in_array($this->curTpl['effect'.$i.'Id'], [53, 54]))
{
if ($mv && ($json = DB::Aowow()->selectRow('SELECT * FROM ?_item_stats WHERE `type` = ?d AND `typeId` = ?d', TYPE_ENCHANTMENT, $mv)))
if ($mv && ($json = DB::Aowow()->selectRow('SELECT * FROM ?_item_stats WHERE `type` = ?d AND `typeId` = ?d', Type::ENCHANTMENT, $mv)))
{
$mods = [];
foreach ($json as $str => $val)
@@ -637,18 +637,18 @@ class SpellList extends BaseType
// GO Model from MiscVal
if (in_array($this->curTpl['effect'.$i.'Id'], [50, 76, 104, 105, 106, 107]))
{
if (isset($displays[TYPE_OBJECT][$id]))
$displays[TYPE_OBJECT][$id][0][] = $i;
if (isset($displays[Type::OBJECT][$id]))
$displays[Type::OBJECT][$id][0][] = $i;
else
$displays[TYPE_OBJECT][$id] = [[$i], $effMV];
$displays[Type::OBJECT][$id] = [[$i], $effMV];
}
// NPC Model from MiscVal
else if (in_array($this->curTpl['effect'.$i.'Id'], [28, 90, 134]) || in_array($this->curTpl['effect'.$i.'AuraId'], [56, 78]))
else if (in_array($this->curTpl['effect'.$i.'Id'], [28, 90, 56, 112, 134]) || in_array($this->curTpl['effect'.$i.'AuraId'], [56, 78]))
{
if (isset($displays[TYPE_NPC][$id]))
$displays[TYPE_NPC][$id][0][] = $i;
if (isset($displays[Type::NPC][$id]))
$displays[Type::NPC][$id][0][] = $i;
else
$displays[TYPE_NPC][$id] = [[$i], $effMV];
$displays[Type::NPC][$id] = [[$i], $effMV];
}
// Shapeshift
else if ($this->curTpl['effect'.$i.'AuraId'] == 36)
@@ -679,17 +679,16 @@ class SpellList extends BaseType
$results = $displays[0];
if (!empty($displays[TYPE_NPC]))
if (!empty($displays[Type::NPC]))
{
$nModels = new CreatureList(array(['id', array_column($displays[TYPE_NPC], 1)]));
$nModels = new CreatureList(array(['id', array_column($displays[Type::NPC], 1)]));
foreach ($nModels->iterate() as $nId => $__)
{
$srcId = 0;
foreach ($displays[TYPE_NPC] as $srcId => $set)
if ($set[1] == $nId)
break;
foreach ($set[0] as $idx)
foreach ($displays[Type::NPC] as $srcId => [$indizes, $npcId])
{
if ($npcId == $nId)
{
foreach ($indizes as $idx)
{
$results[$srcId][$idx] = array(
'typeId' => $nId,
@@ -699,18 +698,19 @@ class SpellList extends BaseType
}
}
}
}
}
if (!empty($displays[TYPE_OBJECT]))
if (!empty($displays[Type::OBJECT]))
{
$oModels = new GameObjectList(array(['id', array_column($displays[TYPE_OBJECT], 1)]));
$oModels = new GameObjectList(array(['id', array_column($displays[Type::OBJECT], 1)]));
foreach ($oModels->iterate() as $oId => $__)
{
$srcId = 0;
foreach ($displays[TYPE_OBJECT] as $srcId => $set)
if ($set[1] == $oId)
break;
foreach ($set[0] as $idx)
foreach ($displays[Type::OBJECT] as $srcId => [$indizes, $objId])
{
if ($objId == $oId)
{
foreach ($indizes as $idx)
{
$results[$srcId][$idx] = array(
'typeId' => $oId,
@@ -720,6 +720,8 @@ class SpellList extends BaseType
}
}
}
}
}
if ($spellId && $effIdx)
return !empty($results[$spellId][$effIdx]) ? $results[$spellId][$effIdx] : 0;
@@ -757,7 +759,7 @@ class SpellList extends BaseType
$pt = $this->curTpl['powerType'];
if ($pt == POWER_RUNE && ($rCost = ($this->curTpl['powerCostRunes'] & 0x333)))
{ // Blood 2|1 - Unholy 2|1 - Frost 2|1
{ // Frost 2|1 - Unholy 2|1 - Blood 2|1
$runes = [];
if ($_ = (($rCost & 0x300) >> 8))
$runes[] = $_.' '.Lang::spell('powerRunes', 2);
@@ -769,7 +771,7 @@ class SpellList extends BaseType
$str .= implode(', ', $runes);
}
else if ($this->curTpl['powerCostPercent'] > 0) // power cost: pct over static
$str .= $this->curTpl['powerCostPercent']."% ".sprintf(Lang::spell('pctCostOf'), strtolower(Lang::spell('powerTypes', $pt)));
$str .= $this->curTpl['powerCostPercent']."% ".sprintf(Lang::spell('pctCostOf'), mb_strtolower(Lang::spell('powerTypes', $pt)));
else if ($this->curTpl['powerCost'] > 0 || $this->curTpl['powerPerSecond'] > 0 || $this->curTpl['powerCostPerLevel'] > 0)
$str .= ($pt == POWER_RAGE || $pt == POWER_RUNIC_POWER ? $this->curTpl['powerCost'] / 10 : $this->curTpl['powerCost']).' '.Util::ucFirst(Lang::spell('powerTypes', $pt));
@@ -789,12 +791,12 @@ class SpellList extends BaseType
if ($this->isChanneledSpell())
return Lang::spell('channeled');
else if ($this->curTpl['castTime'] > 0)
return $short ? sprintf(Lang::spell('castIn'), $this->curTpl['castTime'] / 1000) : Util::formatTime($this->curTpl['castTime']);
return $short ? sprintf(Lang::spell('castIn'), $this->curTpl['castTime']) : Util::formatTime($this->curTpl['castTime'] * 1000);
// show instant only for player/pet/npc abilities (todo (low): unsure when really hidden (like talent-case))
else if ($noInstant && !in_array($this->curTpl['typeCat'], [11, 7, -3, -6, -8, 0]) && !($this->curTpl['cuFlags'] & SPELL_CU_TALENTSPELL))
return '';
// SPELL_ATTR0_ABILITY instant ability.. yeah, wording thing only (todo (low): rule is imperfect)
else if ($this->curTpl['damageClass'] != 1 || $this->curTpl['attributes0'] & 0x10)
else if ($this->curTpl['damageClass'] != 1 || $this->curTpl['attributes0'] & SPELL_ATTR0_ABILITY)
return Lang::spell('instantPhys');
else // instant cast
return Lang::spell('instantMagic');
@@ -813,7 +815,7 @@ class SpellList extends BaseType
// formulae base from TC
private function calculateAmountForCurrent($effIdx, $altTpl = null)
{
$ref = $altTpl ? $altTpl : $this;
$ref = $altTpl ?: $this;
$level = $this->charLevel;
$rppl = $ref->getField('effect'.$effIdx.'RealPointsPerLevel');
$base = $ref->getField('effect'.$effIdx.'BasePoints');
@@ -821,6 +823,7 @@ class SpellList extends BaseType
$maxLvl = $ref->getField('maxLevel');
$baseLvl = $ref->getField('baseLevel');
/* when should level scaling be actively worked into tooltips?
if ($rppl)
{
if ($level > $maxLvl && $maxLvl > 0)
@@ -828,11 +831,12 @@ class SpellList extends BaseType
else if ($level < $baseLvl)
$level = $baseLvl;
if (!$ref->getField('atributes0') & 0x40) // SPELL_ATTR0_PASSIVE
if (!$ref->getField('atributes0') & SPELL_ATTR0_PASSIVE)
$level -= $ref->getField('spellLevel');
$base += (int)($level * $rppl);
}
*/
return [
$add ? $base + 1 : $base,
@@ -877,7 +881,7 @@ class SpellList extends BaseType
public function isChanneledSpell()
{
return $this->curTpl['attributes1'] & 0x44;
return $this->curTpl['attributes1'] & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2);
}
public function isHealingSpell()
@@ -955,7 +959,7 @@ class SpellList extends BaseType
$cond = $COND = function($a, $b, $c) { return $a ? $b : $c; };
$eq = $EQ = function($a, $b) { return $a == $b; };
$gt = $GT = function($a, $b) { return $a > $b; };
$gte = $GTE = function($a, $b) { return $a <= $b; };
$gte = $GTE = function($a, $b) { return $a >= $b; };
$floor = $FLOOR = function($a) { return floor($a); };
$max = $MAX = function($a, $b) { return max($a, $b); };
$min = $MIN = function($a, $b) { return min($a, $b); };
@@ -990,7 +994,7 @@ class SpellList extends BaseType
$cond = $COND = !$this->interactive ? 'COND' : sprintf(Util::$dfnString, 'COND(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>, <span class=\'q1\'>c</span>)<br /> <span class=\'q1\'>a</span> ? <span class=\'q1\'>b</span> : <span class=\'q1\'>c</span>', 'COND');
$eq = $EQ = !$this->interactive ? 'EQ' : sprintf(Util::$dfnString, 'EQ(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> == <span class=\'q1\'>b</span>', 'EQ');
$gt = $GT = !$this->interactive ? 'GT' : sprintf(Util::$dfnString, 'GT(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> > <span class=\'q1\'>b</span>', 'GT');
$gte = $GTE = !$this->interactive ? 'GTE' : sprintf(Util::$dfnString, 'GTE(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> <= <span class=\'q1\'>b</span>', 'GT');
$gte = $GTE = !$this->interactive ? 'GTE' : sprintf(Util::$dfnString, 'GTE(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)<br /> <span class=\'q1\'>a</span> >= <span class=\'q1\'>b</span>', 'GTE');
$floor = $FLOOR = !$this->interactive ? 'FLOOR' : sprintf(Util::$dfnString, 'FLOOR(<span class=\'q1\'>a</span>)', 'FLOOR');
$min = $MIN = !$this->interactive ? 'MIN' : sprintf(Util::$dfnString, 'MIN(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)', 'MIN');
$max = $MAX = !$this->interactive ? 'MAX' : sprintf(Util::$dfnString, 'MAX(<span class=\'q1\'>a</span>, <span class=\'q1\'>b</span>)', 'MAX');
@@ -1205,7 +1209,7 @@ class SpellList extends BaseType
break;
case 'o': // TotalAmount for periodic auras (with variance)
case 'O':
list($min, $max, $modStrMin, $modStrMax) = $this->calculateAmountForCurrent($effIdx, $srcSpell);
[$min, $max, $modStrMin, $modStrMax] = $this->calculateAmountForCurrent($effIdx, $srcSpell);
$periode = $srcSpell->getField('effect'.$effIdx.'Periode');
$duration = $srcSpell->getField('duration');
@@ -1257,7 +1261,7 @@ class SpellList extends BaseType
break;
case 's': // BasePoints (with variance)
case 'S':
list($min, $max, $modStrMin, $modStrMax) = $this->calculateAmountForCurrent($effIdx, $srcSpell);
[$min, $max, $modStrMin, $modStrMax] = $this->calculateAmountForCurrent($effIdx, $srcSpell);
$mv = $srcSpell->getField('effect'.$effIdx.'MiscValue');
$aura = $srcSpell->getField('effect'.$effIdx.'AuraId');
@@ -1352,7 +1356,7 @@ class SpellList extends BaseType
$formOutStr = '';
while ($formCurPos <= strlen($formula))
while ($formCurPos < strlen($formula))
{
$char = $formula[$formCurPos];
@@ -1377,7 +1381,7 @@ class SpellList extends BaseType
++$formCurPos; // for some odd reason the precision decimal survives if we dont increment further..
}
list($formOutStr, $fSuffix, $fRating) = $this->resolveFormulaString($formOutStr, $formPrecision, $scaling);
[$formOutStr, $fSuffix, $fRating] = $this->resolveFormulaString($formOutStr, $formPrecision, $scaling);
$formula = substr_replace($formula, $formOutStr, $formStartPos, ($formCurPos - $formStartPos));
}
@@ -1436,7 +1440,7 @@ class SpellList extends BaseType
// step 3: try to evaluate result
$evaled = $this->resolveEvaluation($str);
$return = is_numeric($evaled) ? Lang::nf($evaled, $precision, true) : $evaled;
$return = is_numeric($evaled) ? round($evaled, $precision) : $evaled;
return [$return, $fSuffix, $fRating];
}
@@ -1646,7 +1650,7 @@ class SpellList extends BaseType
$formOutStr = '';
while ($formCurPos <= strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
while ($formCurPos < strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
{
$char = $data[$formCurPos];
@@ -1674,7 +1678,7 @@ class SpellList extends BaseType
$formPrecision = $data[$formCurPos + 1];
$formCurPos += 2;
}
list($formOutVal, $formOutStr, $ratingId) = $this->resolveFormulaString($formOutStr, $formPrecision ?: ($topLevel ? 0 : 10), $scaling);
[$formOutVal, $formOutStr, $ratingId] = $this->resolveFormulaString($formOutStr, $formPrecision ?: ($topLevel ? 0 : 10), $scaling);
if ($ratingId && Util::checkNumeric($formOutVal) && $this->interactive)
$resolved = sprintf($formOutStr, $ratingId, abs($formOutVal), sprintf(Util::$setRatingLevelString, $this->charLevel, $ratingId, abs($formOutVal), Util::setRatingLevel($this->charLevel, $ratingId, abs($formOutVal))));
@@ -1750,7 +1754,7 @@ class SpellList extends BaseType
$condParts = [];
$isLastElse = false;
while ($condCurPos <= strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
while ($condCurPos < strlen($data)) // only hard-exit condition, we'll hit those breaks eventually^^
{
$char = $data[$condCurPos];
@@ -1912,9 +1916,9 @@ class SpellList extends BaseType
$reagents = array_reverse($reagents);
// get stances (check: SPELL_ATTR2_NOT_NEED_SHAPESHIFT)
// get stances
$stances = '';
if ($this->curTpl['stanceMask'] && !($this->curTpl['attributes2'] & 0x80000))
if ($this->curTpl['stanceMask'] && !($this->curTpl['attributes2'] & SPELL_ATTR2_NOT_NEED_SHAPESHIFT))
$stances = Lang::game('requires2').' '.Lang::getStances($this->curTpl['stanceMask']);
// get item requirement (skip for professions)
@@ -2074,23 +2078,23 @@ class SpellList extends BaseType
return $x;
}
public function getColorsForCurrent()
public function getColorsForCurrent() : array
{
$gry = $this->curTpl['skillLevelGrey'];
$ylw = $this->curTpl['skillLevelYellow'];
$grn = (int)(($ylw + $gry) / 2);
$org = $this->curTpl['learnedAt'];
if (($org && $ylw < $org) || $ylw >= $gry)
if ($ylw < $org)
$ylw = 0;
if (($org && $grn < $org) || $grn >= $gry)
if ($grn < $org || $grn < $ylw)
$grn = 0;
if (($grn && $org >= $grn) || $org >= $gry)
if ($org >= $ylw || $org >= $grn || $org >= $gry)
$org = 0;
return $gry > 1 ? [$org, $ylw, $grn, $gry] : null;
return $gry > 1 ? [$org, $ylw, $grn, $gry] : [];
}
public function getListviewData($addInfoMask = 0x0)
@@ -2201,24 +2205,24 @@ class SpellList extends BaseType
if ($mask = $this->curTpl['reqClassMask'])
for ($i = 0; $i < 11; $i++)
if ($mask & (1 << $i))
$data[TYPE_CLASS][$i + 1] = $i + 1;
$data[Type::CHR_CLASS][$i + 1] = $i + 1;
if ($mask = $this->curTpl['reqRaceMask'])
for ($i = 0; $i < 11; $i++)
if ($mask & (1 << $i))
$data[TYPE_RACE][$i + 1] = $i + 1;
$data[Type::CHR_RACE][$i + 1] = $i + 1;
// play sound effect
for ($i = 1; $i < 4; $i++)
if ($this->getField('effect'.$i.'Id') == 131 || $this->getField('effect'.$i.'Id') == 132)
$data[TYPE_SOUND][$this->getField('effect'.$i.'MiscValue')] = $this->getField('effect'.$i.'MiscValue');
$data[Type::SOUND][$this->getField('effect'.$i.'MiscValue')] = $this->getField('effect'.$i.'MiscValue');
}
if ($addMask & GLOBALINFO_SELF)
{
$iconString = $this->curTpl['iconStringAlt'] ? 'iconStringAlt' : 'iconString';
$data[TYPE_SPELL][$id] = array(
$data[Type::SPELL][$id] = array(
'icon' => $this->curTpl[$iconString],
'name' => $this->getField('name', true),
);
@@ -2230,12 +2234,12 @@ class SpellList extends BaseType
$tTip = $this->renderTooltip(MAX_LEVEL, true);
foreach ($tTip[1] as $relId => $_)
if (empty($data[TYPE_SPELL][$relId]))
$data[TYPE_SPELL][$relId] = $relId;
if (empty($data[Type::SPELL][$relId]))
$data[Type::SPELL][$relId] = $relId;
foreach ($buff[1] as $relId => $_)
if (empty($data[TYPE_SPELL][$relId]))
$data[TYPE_SPELL][$relId] = $relId;
if (empty($data[Type::SPELL][$relId]))
$data[Type::SPELL][$relId] = $relId;
$extra[$id] = array(
'id' => $id,
@@ -2254,7 +2258,7 @@ class SpellList extends BaseType
public function getCastingTimeForBonus($asDOT = false)
{
$areaTargets = [7, 8, 15, 16, 20, 24, 30, 31, 33, 34, 37, 54, 56, 59, 104, 108];
$castingTime = $this->IsChanneledSpell() ? $this->curTpl['duration'] : $this->curTpl['castTime'];
$castingTime = $this->IsChanneledSpell() ? $this->curTpl['duration'] : ($this->curTpl['castTime'] * 1000);
if (!$castingTime)
return 3500;
@@ -2291,8 +2295,8 @@ class SpellList extends BaseType
if ($overTime > 0 && $castingTime > 0 && $isDirect)
{
// mainly for DoTs which are 3500 here otherwise
$originalCastTime = $this->curTpl['castTime'];
if ($this->curTpl['attributes0'] & 0x2) // requires Ammo
$originalCastTime = $this->curTpl['castTime'] * 1000;
if ($this->curTpl['attributes0'] & SPELL_ATTR0_REQ_AMMO)
$originalCastTime += 500;
if ($originalCastTime > 7000)
@@ -2332,7 +2336,7 @@ class SpellList extends BaseType
{
$data[$this->id] = array(
'n' => $this->getField('name', true),
't' => TYPE_SPELL,
't' => Type::SPELL,
'ti' => $this->id,
's' => empty($this->curTpl['skillLines']) ? 0 : $this->curTpl['skillLines'][0],
'c' => $this->curTpl['typeCat'],
@@ -2347,9 +2351,11 @@ class SpellList extends BaseType
class SpellListFilter extends Filter
{
// sources in filter and general use different indizes
private $enums = array(
9 => array(
const MAX_SPELL_EFFECT = 167;
const MAX_SPELL_AURA = 316;
protected $enums = array(
9 => array( // sources index
1 => true, // Any
2 => false, // None
3 => 1, // Crafted
@@ -2359,18 +2365,47 @@ class SpellListFilter extends Filter
8 => 6, // Trainer
9 => 7, // Discovery
10 => 9 // Talent
),
40 => array( // damage class index
1 => 0, // none
2 => 1, // magic
3 => 2, // melee
4 => 3 // ranged
),
45 => array( // power type index
// 1 => ??, // burning embers
// 2 => ??, // chi
// 3 => ??, // demonic fury
4 => POWER_ENERGY, // energy
5 => POWER_FOCUS, // focus
6 => POWER_HEALTH, // health
// 7 => ??, // holy power
8 => POWER_MANA, // mana
9 => POWER_RAGE, // rage
10 => POWER_RUNE, // runes
11 => POWER_RUNIC_POWER, // runic power
// 12 => ??, // shadow orbs
// 13 => ??, // soul shard
14 => POWER_HAPPINESS, // happiness v custom v
15 => -1, // ammo
16 => -41, // pyrite
17 => -61, // steam pressure
18 => -101, // heat
19 => -121, // ooze
20 => -141, // blood power
21 => -142 // wrath
)
);
// cr => [type, field, misc, extraCol]
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
1 => [FILTER_CR_CALLBACK, 'cbCost', null, null], // costAbs [op] [int]
1 => [FILTER_CR_CALLBACK, 'cbCost', ], // costAbs [op] [int]
2 => [FILTER_CR_NUMERIC, 'powerCostPercent', NUM_CAST_INT ], // prcntbasemanarequired
3 => [FILTER_CR_BOOLEAN, 'spellFocusObject' ], // requiresnearbyobject
4 => [FILTER_CR_NUMERIC, 'trainingcost', NUM_CAST_INT ], // trainingcost
5 => [FILTER_CR_BOOLEAN, 'reqSpellId' ], // requiresprofspec
8 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
9 => [FILTER_CR_CALLBACK, 'cbSource', null, null], // source [enum]
9 => [FILTER_CR_CALLBACK, 'cbSource', ], // source [enum]
10 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_FIRST_RANK ], // firstrank
11 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
12 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_LAST_RANK ], // lastrank
@@ -2378,17 +2413,99 @@ class SpellListFilter extends Filter
14 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id
15 => [FILTER_CR_STRING, 'ic.name', ], // icon
17 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
19 => [FILTER_CR_FLAG, 'attributes0', 0x80000 ], // scaling
20 => [FILTER_CR_CALLBACK, 'cbReagents', null, null], // has Reagents [yn]
25 => [FILTER_CR_BOOLEAN, 'skillLevelYellow' ] // rewardsskillups
19 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION ], // scaling
20 => [FILTER_CR_CALLBACK, 'cbReagents', ], // has Reagents [yn]
// 22 => [FILTER_CR_NYI_PH, null, null, null ], // proficiencytype [proficiencytype] pointless
25 => [FILTER_CR_BOOLEAN, 'skillLevelYellow' ], // rewardsskillups
27 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CHANNELED_1, true ], // channeled [yn]
28 => [FILTER_CR_NUMERIC, 'castTime', NUM_CAST_FLOAT ], // casttime [num]
29 => [FILTER_CR_CALLBACK, 'cbAuraNames', ], // appliesaura [effectauranames]
// 31 => [FILTER_CR_NYI_PH, null, null, null ], // usablewhenshapeshifted [yn] pointless
33 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes0', SPELL_ATTR0_CANT_USED_IN_COMBAT], // combatcastable [yn]
34 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes2', SPELL_ATTR2_CANT_CRIT ], // chancetocrit [yn]
35 => [FILTER_CR_CALLBACK, 'cbInverseFlag', 'attributes3', SPELL_ATTR3_IGNORE_HIT_RESULT ], // chancetomiss [yn]
36 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_DEATH_PERSISTENT ], // persiststhroughdeath [yn]
38 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ONLY_STEALTHED ], // requiresstealth [yn]
39 => [FILTER_CR_CALLBACK, 'cbSpellstealable', 'attributes4', SPELL_ATTR4_NOT_STEALABLE ], // spellstealable [yn]
40 => [FILTER_CR_ENUM, 'damageClass' ], // damagetype [damagetype]
41 => [FILTER_CR_FLAG, 'stanceMask', (1 << (22 - 1)) ], // requiresmetamorphosis [yn]
42 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_STUNNED ], // usablewhenstunned [yn]
44 => [FILTER_CR_CALLBACK, 'cbUsableInArena' ], // usableinarenas [yn]
45 => [FILTER_CR_ENUM, 'powerType' ], // resourcetype [resourcetype]
// 46 => [FILTER_CR_NYI_PH, null, null, null ], // disregardimmunity [yn]
47 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE ], // disregardschoolimmunity [yn]
48 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x0004000C, false ], // reqrangedweapon [yn]
49 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ON_NEXT_SWING ], // onnextswingplayers [yn]
50 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_PASSIVE ], // passivespell [yn]
51 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR ], // hiddenaura [yn]
52 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_ON_NEXT_SWING_2 ], // onnextswingnpcs [yn]
53 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_DAYTIME_ONLY ], // daytimeonly [yn]
54 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_NIGHT_ONLY ], // nighttimeonly [yn]
55 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_INDOORS_ONLY ], // indoorsonly [yn]
56 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_OUTDOORS_ONLY ], // outdoorsonly [yn]
57 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CANT_CANCEL ], // uncancellableaura [yn]
58 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION ], // damagedependsonlevel [yn]
59 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_STOP_ATTACK_TARGET ], // stopsautoattack [yn]
60 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK ], // cannotavoid [yn]
61 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_DEAD ], // usabledead [yn]
62 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_MOUNTED ], // usablemounted [yn]
63 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_DISABLED_WHILE_ACTIVE ], // delayedrecoverystarttime [yn]
64 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_CASTABLE_WHILE_SITTING ], // usablesitting [yn]
65 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DRAIN_ALL_POWER ], // usesallpower [yn]
66 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CHANNELED_2, true ], // channeled [yn]
67 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CANT_BE_REFLECTED ], // cannotreflect [yn]
68 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_NOT_BREAK_STEALTH ], // usablestealthed [yn]
69 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_NEGATIVE_1 ], // harmful [yn]
70 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_CANT_TARGET_IN_COMBAT ], // targetnotincombat [yn]
71 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_NO_THREAT ], // nothreat [yn]
72 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_IS_PICKPOCKET ], // pickpocket [yn]
73 => [FILTER_CR_FLAG, 'attributes1', SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY ], // dispelauraonimmunity [yn]
74 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x00100000, false ], // reqfishingpole [yn]
75 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_CANT_TARGET_TAPPED ], // requntappedtarget [yn]
// 76 => [FILTER_CR_NYI_PH, null, null, null ], // targetownitem [yn] // the flag for this has to be somewhere....
77 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_NOT_NEED_SHAPESHIFT ], // doesntreqshapeshift [yn]
78 => [FILTER_CR_FLAG, 'attributes2', SPELL_ATTR2_FOOD_BUFF ], // foodbuff [yn]
79 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_ONLY_TARGET_PLAYERS ], // targetonlyplayer [yn]
80 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 1 << INVTYPE_WEAPONMAINHAND, true ], // reqmainhand [yn]
81 => [FILTER_CR_FLAG, 'attributes3', SPELL_ATTR3_NO_INITIAL_AGGRO ], // doesntengagetarget [yn]
82 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 0x00080000, false ], // reqwand [yn]
83 => [FILTER_CR_CALLBACK, 'cbEquippedWeapon', 1 << INVTYPE_WEAPONOFFHAND, true ], // reqoffhand [yn]
84 => [FILTER_CR_FLAG, 'attributes0', SPELL_ATTR0_HIDE_IN_COMBAT_LOG ], // nolog [yn]
85 => [FILTER_CR_FLAG, 'attributes4', SPELL_ATTR4_FADES_WHILE_LOGGED_OUT ], // auratickswhileloggedout [yn]
87 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_START_PERIODIC_AT_APPLY ], // startstickingatapplication [yn]
88 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_CONFUSED ], // usableconfused [yn]
89 => [FILTER_CR_FLAG, 'attributes5', SPELL_ATTR5_USABLE_WHILE_FEARED ], // usablefeared [yn]
90 => [FILTER_CR_FLAG, 'attributes6', SPELL_ATTR6_ONLY_IN_ARENA ], // onlyarena [yn]
91 => [FILTER_CR_FLAG, 'attributes6', SPELL_ATTR6_NOT_IN_RAID_INSTANCE ], // notinraid [yn]
92 => [FILTER_CR_FLAG, 'attributes7', SPELL_ATTR7_REACTIVATE_AT_RESURRECT ], // paladinaura [yn]
93 => [FILTER_CR_FLAG, 'attributes7', SPELL_ATTR7_SUMMON_PLAYER_TOTEM ], // totemspell [yn]
95 => [FILTER_CR_CALLBACK, 'cbBandageSpell' ], // bandagespell [yn] ...don't ask
96 => [FILTER_CR_STAFFFLAG, 'attributes0' ], // flags1 [flags]
97 => [FILTER_CR_STAFFFLAG, 'attributes1' ], // flags2 [flags]
98 => [FILTER_CR_STAFFFLAG, 'attributes2' ], // flags3 [flags]
99 => [FILTER_CR_STAFFFLAG, 'attributes3' ], // flags4 [flags]
100 => [FILTER_CR_STAFFFLAG, 'attributes4' ], // flags5 [flags]
101 => [FILTER_CR_STAFFFLAG, 'attributes5' ], // flags6 [flags]
102 => [FILTER_CR_STAFFFLAG, 'attributes6' ], // flags7 [flags]
103 => [FILTER_CR_STAFFFLAG, 'attributes7' ], // flags8 [flags]
104 => [FILTER_CR_STAFFFLAG, 'targets' ], // flags9 [flags]
105 => [FILTER_CR_STAFFFLAG, 'stanceMaskNot' ], // flags10 [flags]
106 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags1' ], // flags11 [flags]
107 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags2' ], // flags12 [flags]
108 => [FILTER_CR_STAFFFLAG, 'spellFamilyFlags3' ], // flags13 [flags]
109 => [FILTER_CR_CALLBACK, 'cbEffectNames', ], // effecttype [effecttype]
// 110 => [FILTER_CR_NYI_PH, null, null, null ], // scalingap [yn] // unreasonably complex for now
// 111 => [FILTER_CR_NYI_PH, null, null, null ], // scalingsp [yn] // unreasonably complex for now
114 => [FILTER_CR_CALLBACK, 'cbReqFaction' ], // requiresfaction [side]
116 => [FILTER_CR_BOOLEAN, 'startRecoveryTime' ] // onGlobalCooldown [yn]
);
// fieldId => [checkType, checkValue[, fieldIsArray]]
protected $inputFields = array(
'cr' => [FILTER_V_RANGE, [1, 25], true ], // criteria ids
'cr' => [FILTER_V_RANGE, [1, 116], true ], // criteria ids
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
'crv' => [FILTER_V_REGEX, '/[\p{C};:]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name / text - only printable chars, no delimiter
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / text - only printable chars, no delimiter
'ex' => [FILTER_V_EQUAL, 'on', false], // extended name search
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
'minle' => [FILTER_V_RANGE, [1, 99], false], // spell level min
@@ -2475,6 +2592,11 @@ class SpellListFilter extends Filter
return $parts;
}
public function getGenericFilter($cr) // access required by SpellDetailPage's SpellAttributes list
{
return $this->genericFilter[$cr] ?? [];
}
protected function cbClasses(&$val)
{
if (!$this->parentCats || !in_array($this->parentCats[0], [-13, -2, 7]))
@@ -2508,7 +2630,10 @@ class SpellListFilter extends Filter
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
return false;
return ['OR', ['AND', ['powerType', [1, 6]], ['powerCost', (10 * $cr[2]), $cr[1]]], ['AND', ['powerType', [1, 6], '!'], ['powerCost', $cr[2], $cr[1]]]];
return ['OR',
['AND', ['powerType', [POWER_RAGE, POWER_RUNIC_POWER]], ['powerCost', (10 * $cr[2]), $cr[1]]],
['AND', ['powerType', [POWER_RAGE, POWER_RUNIC_POWER], '!'], ['powerCost', $cr[2], $cr[1]]]
];
}
protected function cbSource($cr)
@@ -2520,9 +2645,9 @@ class SpellListFilter extends Filter
if (is_int($_)) // specific
return ['src.src'.$_, null, '!'];
else if ($_) // any
return ['OR', ['src.src1', null, '!'], ['src.src2', null, '!'], ['src.src4', null, '!'], ['src.src5', null, '!'], ['src.src6', null, '!'], ['src.src7', null, '!'], ['src.src9', null, '!']];
return ['OR', ['src.src1', null, '!'], ['src.src2', null, '!'], ['src.src4', null, '!'], ['src.src5', null, '!'], ['src.src6', null, '!'], ['src.src7', null, '!'], ['src.src9', null, '!'], ['src.src10', null, '!']];
else if (!$_) // none
return ['AND', ['src.src1', null], ['src.src2', null], ['src.src4', null], ['src.src5', null], ['src.src6', null], ['src.src7', null], ['src.src9', null]];
return ['AND', ['src.src1', null], ['src.src2', null], ['src.src4', null], ['src.src5', null], ['src.src6', null], ['src.src7', null], ['src.src9', null], ['src.src10', null]];
return false;
}
@@ -2537,6 +2662,104 @@ class SpellListFilter extends Filter
else
return ['AND', ['reagent1', 0], ['reagent2', 0], ['reagent3', 0], ['reagent4', 0], ['reagent5', 0], ['reagent6', 0], ['reagent7', 0], ['reagent8', 0]];
}
protected function cbAuraNames($cr)
{
if (!Util::checkNumeric($cr[1], NUM_CAST_INT) || $cr[1] <= 0 || $cr[1] > self::MAX_SPELL_AURA)
return false;
return ['OR', ['effect1AuraId', $cr[1]], ['effect2AuraId', $cr[1]], ['effect3AuraId', $cr[1]]];
}
protected function cbEffectNames($cr)
{
if (!Util::checkNumeric($cr[1], NUM_CAST_INT) || $cr[1] <= 0 || $cr[1] > self::MAX_SPELL_EFFECT)
return false;
return ['OR', ['effect1Id', $cr[1]], ['effect2Id', $cr[1]], ['effect3Id', $cr[1]]];
}
protected function cbInverseFlag($cr, $field, $flag)
{
if (!$this->int2Bool($cr[1]))
return false;
if ($cr[1])
return [[$field, $flag, '&'], 0];
else
return [$field, $flag, '&'];
}
protected function cbSpellstealable($cr, $field, $flag)
{
if (!$this->int2Bool($cr[1]))
return false;
if ($cr[1])
return ['AND', [[$field, $flag, '&'], 0], ['dispelType', 1]];
else
return ['OR', [$field, $flag, '&'], ['dispelType', 1, '!']];
}
protected function cbReqFaction($cr)
{
switch ($cr[1])
{
case 1: // yes
return ['reqRaceMask', 0, '!'];
case 2: // alliance
return ['AND', [['reqRaceMask', RACE_MASK_HORDE, '&'], 0], ['reqRaceMask', RACE_MASK_ALLIANCE, '&']];
case 3: // horde
return ['AND', [['reqRaceMask', RACE_MASK_ALLIANCE, '&'], 0], ['reqRaceMask', RACE_MASK_HORDE, '&']];
case 4: // both
return ['AND', ['reqRaceMask', RACE_MASK_ALLIANCE, '&'], ['reqRaceMask', RACE_MASK_HORDE, '&']];
case 5: // no
return ['reqRaceMask', 0];
default:
return false;
}
}
protected function cbEquippedWeapon($cr, $mask, $useInvType)
{
if (!$this->int2Bool($cr[1]))
return false;
$field = $useInvType ? 'equippedItemInventoryTypeMask' : 'equippedItemSubClassMask';
if ($cr[1])
return ['AND', ['equippedItemClass', ITEM_CLASS_WEAPON], [$field, $mask, '&']];
else
return ['OR', ['equippedItemClass', ITEM_CLASS_WEAPON, '!'], [[$field, $mask, '&'], 0]];
}
protected function cbUsableInArena($cr)
{
if (!$this->int2Bool($cr[1]))
return false;
if ($cr[1])
return ['AND',
[['attributes4', SPELL_ATTR4_NOT_USABLE_IN_ARENA, '&'], 0],
['OR', ['recoveryTime', 10 * MINUTE * 1000, '<='], ['attributes4', SPELL_ATTR4_USABLE_IN_ARENA, '&']]
];
else
return ['OR',
['attributes4', SPELL_ATTR4_NOT_USABLE_IN_ARENA, '&'],
['AND', ['recoveryTime', 10 * MINUTE * 1000, '>'], [['attributes4', SPELL_ATTR4_USABLE_IN_ARENA, '&'], 0]]
];
}
protected function cbBandageSpell($cr)
{
if (!$this->int2Bool($cr[1]))
return false;
if ($cr[1]) // match exact, not as flag
return ['AND', ['attributes1', SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_CHANNEL_TRACK_TARGET], ['effect1ImplicitTargetA', 21]];
else
return ['OR', ['attributes1', SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_CHANNEL_TRACK_TARGET, '!'], ['effect1ImplicitTargetA', 21, '!']];
}
}
?>

View File

@@ -8,7 +8,7 @@ class TitleList extends BaseType
{
use listviewHelper;
public static $type = TYPE_TITLE;
public static $type = Type::TITLE;
public static $brickFile = 'title';
public static $dataTable = '?_titles';
@@ -16,7 +16,7 @@ class TitleList extends BaseType
protected $queryBase = 'SELECT t.*, id AS ARRAY_KEY FROM ?_titles t';
protected $queryOpts = array(
't' => [['src']], // 11: TYPE_TITLE
't' => [['src']], // 11: Type::TITLE
'src' => ['j' => ['?_source src ON type = 11 AND typeId = t.id', true], 's' => ', src13, moreType, moreTypeId']
);
@@ -28,9 +28,9 @@ class TitleList extends BaseType
foreach ($this->iterate() as $id => &$_curTpl)
{
// preparse sources - notice: under this system titles can't have more than one source (or two for achivements), which is enough for standard TC cases but may break custom cases
if ($_curTpl['moreType'] == TYPE_ACHIEVEMENT)
if ($_curTpl['moreType'] == Type::ACHIEVEMENT)
$this->sources[$this->id][12][] = $_curTpl['moreTypeId'];
else if ($_curTpl['moreType'] == TYPE_QUEST)
else if ($_curTpl['moreType'] == Type::QUEST)
$this->sources[$this->id][4][] = $_curTpl['moreTypeId'];
else if ($_curTpl['src13'])
$this->sources[$this->id][13][] = $_curTpl['src13'];
@@ -81,10 +81,10 @@ class TitleList extends BaseType
foreach ($this->iterate() as $__)
{
$data[TYPE_TITLE][$this->id]['name'] = $this->getField('male', true);
$data[Type::TITLE][$this->id]['name'] = $this->getField('male', true);
if ($_ = $this->getField('female', true))
$data[TYPE_TITLE][$this->id]['namefemale'] = $_;
$data[Type::TITLE][$this->id]['namefemale'] = $_;
}
return $data;
@@ -115,9 +115,6 @@ class TitleList extends BaseType
if (!empty($sources[12]))
$sources[12] = (new AchievementList(array(['id', $sources[12]])))->getSourceData();
if (!empty($sources[13]))
$sources[13] = DB::Aowow()->SELECT('SELECT *, Id AS ARRAY_KEY FROM ?_sourcestrings WHERE Id IN (?a)', $sources[13]);
foreach ($this->sources as $Id => $src)
{
$tmp = [];
@@ -148,7 +145,7 @@ class TitleList extends BaseType
// other source (only one item possible, so no iteration needed)
if (isset($src[13]))
$tmp[13] = [Util::localizedString($sources[13][$this->sources[$Id][13][0]], 'source')];
$tmp[13] = [Lang::game('pvpSources', $this->sources[$Id][13][0])];
$this->templates[$Id]['source'] = $tmp;
}

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class UserList extends BaseType
{
public static $type = TYPE_USER;
public static $type = Type::USER;
public static $brickFile = 'user';
public static $dataTable = ''; // doesn't have community content
@@ -52,7 +52,7 @@ class UserList extends BaseType
// border: seen as null|1|3 .. changes the border around the avatar (i suspect its meaning changed and got decupled from premium-status with the introduction of patron-status)
}
return [TYPE_USER => $data];
return [Type::USER => $data];
}
public function renderTooltip() { }

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class WorldEventList extends BaseType
{
public static $type = TYPE_WORLDEVENT;
public static $type = Type::WORLDEVENT;
public static $brickFile = 'event';
public static $dataTable = '?_events';
@@ -71,6 +71,7 @@ class WorldEventList extends BaseType
IFNULL(h.name_loc0, e.description) AS name_loc0,
h.name_loc2,
h.name_loc3,
h.name_loc4,
h.name_loc6,
h.name_loc8
FROM
@@ -161,7 +162,7 @@ class WorldEventList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_WORLDEVENT][$this->id] = ['name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString']];
$data[Type::WORLDEVENT][$this->id] = ['name' => $this->getField('name', true), 'icon' => $this->curTpl['iconString']];
return $data;
}
@@ -174,7 +175,7 @@ class WorldEventList extends BaseType
$x = '<table><tr><td>';
// head v that extra % is nesecary because we are using sprintf later on
$x .= '<table width="100%%"><tr><td><b>'.Util::jsEscape($this->getField('name', true)).'</b></td><th><b class="q0">'.Lang::event('category', $this->getField('category')).'</b></th></tr></table>';
$x .= '<table width="100%%"><tr><td><b>'.$this->getField('name', true).'</b></td><th><b class="q0">'.Lang::event('category', $this->getField('category')).'</b></th></tr></table>';
// use string-placeholder for dates
// start
@@ -187,7 +188,7 @@ class WorldEventList extends BaseType
// desc
if ($this->getField('holidayId'))
if ($_ = $this->getField('description', true))
$x .= '<table><tr><td><span class="q">'.Util::jsEscape($_).'</span></td></tr></table>';
$x .= '<table><tr><td><span class="q">'.$_.'</span></td></tr></table>';
return $x;
}

View File

@@ -8,11 +8,11 @@ class ZoneList extends BaseType
{
use listviewHelper;
public static $type = TYPE_ZONE;
public static $type = Type::ZONE;
public static $brickFile = 'zone';
public static $dataTable = '?_zones';
protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_zones z';
protected $queryBase = 'SELECT z.*, id AS ARRAY_KEY FROM ?_zones z';
public function __construct($conditions = [], $miscData = null)
{
@@ -54,7 +54,7 @@ class ZoneList extends BaseType
// use if you JUST need the name
public static function getName($id)
{
$n = DB::Aowow()->selectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM ?_zones WHERE id = ?d', $id );
$n = DB::Aowow()->selectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_zones WHERE id = ?d', $id );
return Util::localizedString($n, 'name');
}
@@ -100,7 +100,7 @@ class ZoneList extends BaseType
$data = [];
foreach ($this->iterate() as $__)
$data[TYPE_ZONE][$this->id] = ['name' => $this->getField('name', true)];
$data[Type::ZONE][$this->id] = ['name' => $this->getField('name', true)];
return $data;
}

View File

@@ -183,10 +183,11 @@ class User
{
$loc = strtolower(substr($_SERVER["HTTP_ACCEPT_LANGUAGE"], 0, 2));
switch ($loc) {
case 'ru': $loc = LOCALE_RU; break;
case 'es': $loc = LOCALE_ES; break;
case 'de': $loc = LOCALE_DE; break;
case 'fr': $loc = LOCALE_FR; break;
case 'de': $loc = LOCALE_DE; break;
case 'zh': $loc = LOCALE_CN; break; // may cause issues in future with zh-tw
case 'es': $loc = LOCALE_ES; break;
case 'ru': $loc = LOCALE_RU; break;
default: $loc = LOCALE_EN;
}
}
@@ -282,12 +283,11 @@ class User
if (!DB::isConnectable(DB_AUTH))
return AUTH_INTERNAL_ERR;
$wow = DB::Auth()->selectRow('SELECT a.id, a.sha_pass_hash, ab.active AS hasBan FROM account a LEFT JOIN account_banned ab ON ab.id = a.id AND active <> 0 WHERE username = ? LIMIT 1', $name);
$wow = DB::Auth()->selectRow('SELECT a.id, a.salt, a.verifier, ab.active AS hasBan FROM account a LEFT JOIN account_banned ab ON ab.id = a.id AND active <> 0 WHERE username = ? LIMIT 1', $name);
if (!$wow)
return AUTH_WRONGUSER;
self::$passHash = $wow['sha_pass_hash'];
if (!self::verifySHA1($name, $pass))
if (!self::verifySRP6($name, $pass, $wow['salt'], $wow['verifier']))
return AUTH_WRONGPASS;
if ($wow['hasBan'])
@@ -386,15 +386,17 @@ class User
return $_ === crypt($pass, $_);
}
// sha1 used by TC / MaNGOS
private static function hashSHA1($name, $pass)
private static function verifySRP6($user, $pass, $salt, $verifier)
{
return sha1(strtoupper($name).':'.strtoupper($pass));
}
private static function verifySHA1($name, $pass)
{
return strtoupper(self::$passHash) === strtoupper(self::hashSHA1($name, $pass));
$g = gmp_init(7);
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
$x = gmp_import(
sha1($salt . sha1(strtoupper($user . ':' . $pass), TRUE), TRUE),
1,
GMP_LSW_FIRST
);
$v = gmp_powm($g, $x, $N);
return ($verifier === str_pad(gmp_export($v, 1, GMP_LSW_FIRST), 32, chr(0), STR_PAD_RIGHT));
}
public static function isValidName($name, &$errCode = 0)
@@ -516,6 +518,14 @@ class User
return true;
}
public static function canWriteGuide()
{
if (!self::$id || self::$banStatus & (ACC_BAN_GUIDE | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
return true;
}
public static function canSuggestVideo()
{
if (!self::$id || self::$banStatus & (ACC_BAN_VIDEO | ACC_BAN_PERM | ACC_BAN_TEMP))
@@ -587,6 +597,9 @@ class User
if ($_ = self::getProfiles())
$gUser['profiles'] = $_;
if ($_ = self::getGuides())
$gUser['guides'] = $_;
if ($_ = self::getWeightScales())
$gUser['weightscales'] = $_;
@@ -639,6 +652,20 @@ class User
return self::$profiles->getJSGlobals(PROFILEINFO_PROFILE);
}
public static function getGuides()
{
$result = [];
if ($guides = DB::Aowow()->select('SELECT `id`, `title`, `url` FROM ?_guides WHERE `userId` = ?d AND `status` <> ?d', self::$id, GUIDE_STATUS_ARCHIVED))
{
// fix url
array_walk($guides, fn(&$x) => $x['url'] = '/?guide='.($x['url'] ?? $x['id']));
$result = $guides;
}
return $result;
}
public static function getCookies()
{
$data = [];
@@ -648,6 +675,33 @@ class User
return $data;
}
public static function getFavorites()
{
if (!self::$id)
return [];
$res = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `typeId` FROM ?_account_favorites WHERE `userId` = ?d', self::$id);
if (!$res)
return [];
$data = [];
foreach ($res as $type => $ids)
{
$tc = Type::newList($type, [['id', array_values($ids)]]);
if (!$tc || $tc->error)
continue;
$entities = [];
foreach ($tc->iterate() as $id => $__)
$entities[] = [$id, $tc->getField('name', true, true)];
if ($entities)
$data[] = ['id' => $type, 'entities' => $entities];
}
return $data;
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,8 @@ switch ($pageCall)
case 'account': // account management [nyi]
case 'achievement':
case 'achievements':
case 'areatrigger':
case 'areatriggers':
case 'arena-team':
case 'arena-teams':
case 'class':
@@ -37,6 +39,8 @@ switch ($pageCall)
case 'events':
case 'faction':
case 'factions':
case 'guide':
case 'guides':
case 'guild':
case 'guilds':
case 'icon':
@@ -46,6 +50,11 @@ switch ($pageCall)
case 'itemset':
case 'itemsets':
case 'maps': // tool: map listing
case 'mail':
case 'mails':
case 'my-guides':
if ($pageCall == 'my-guides')
$altClass = 'guides';
case 'npc':
case 'npcs':
case 'object':
@@ -82,14 +91,18 @@ switch ($pageCall)
case 'cookie': // lossless cookies and user settings
case 'contactus':
case 'comment':
case 'edit': // guide editor: targeted by QQ fileuploader, detail-page article editor
case 'get-description': // guide editor: shorten fulltext into description
case 'filter': // pre-evaluate filter POST-data; sanitize and forward as GET-data
case 'go-to-comment': // find page the comment is on and forward
case 'locale': // subdomain-workaround, change the language
$cleanName = str_replace(['-', '_'], '', ucFirst($altClass ?: $pageCall));
try // can it be handled as ajax?
{
$out = '';
$class = 'Ajax'.$cleanName;
$ajax = new $class(explode('.', $pageParam));
if ($ajax->handle($out))
{
Util::sendNoCacheHeader();
@@ -98,7 +111,7 @@ switch ($pageCall)
header('Location: '.$out, true, 302);
else
{
header('Content-type: '.$ajax->getContentType());
header($ajax->getContentType());
die($out);
}
}
@@ -108,7 +121,14 @@ switch ($pageCall)
catch (Exception $e) // no, apparently not..
{
$class = $cleanName.'Page';
(new $class($pageCall, $pageParam))->display();
$classInstance = new $class($pageCall, $pageParam);
if (is_callable([$classInstance, 'display']))
$classInstance->display();
else if (isset($_GET['power']))
die('$WowheadPower.register(0, '.User::$localeId.', {})');
else // in conjunction with a proper rewriteRule in .htaccess...
(new GenericPage($pageCall))->error();
}
break;
@@ -127,7 +147,6 @@ switch ($pageCall)
(new MorePage($pageCall, $pageParam))->display();
break;
case 'latest-additions':
case 'latest-articles':
case 'latest-comments':
case 'latest-screenshots':
case 'latest-videos':

View File

@@ -6,15 +6,17 @@ class Lang
private static $main;
private static $account;
private static $user;
private static $mail;
private static $game;
private static $maps;
private static $profiler;
private static $screenshot;
private static $privileges;
private static $smartAI;
private static $unit;
// types
private static $achievement;
private static $areatrigger;
private static $chrClass;
private static $currency;
private static $event;
@@ -23,6 +25,7 @@ class Lang
private static $icon;
private static $item;
private static $itemset;
private static $mail;
private static $npc;
private static $pet;
private static $quest;
@@ -32,10 +35,20 @@ class Lang
private static $spell;
private static $title;
private static $zone;
private static $guide;
private static $emote;
private static $enchantment;
private static $locales = array(
LOCALE_EN => 'English',
LOCALE_FR => 'Français',
LOCALE_DE => 'Deutsch',
LOCALE_CN => '简体中文',
LOCALE_ES => 'Español',
LOCALE_RU => 'Русский'
);
public static function load($loc)
{
if (!file_exists('localization/locale_'.$loc.'.php'))
@@ -49,13 +62,6 @@ class Lang
// *cough* .. reuse-hacks (because copy-pastaing text for 5 locales sucks)
self::$item['cat'][2] = [self::$item['cat'][2], self::$spell['weaponSubClass']];
self::$item['cat'][2][1][14] .= ' ('.self::$item['cat'][2][0].')';
// not localized .. for whatever reason
self::$profiler['regions'] = array(
'eu' => "Europe",
'us' => "US & Oceanic"
);
self::$main['moreTitles']['privilege'] = self::$privileges['_privileges'];
}
@@ -63,7 +69,9 @@ class Lang
{
if (!isset(self::$$prop))
{
trigger_error('Lang - tried to use undefined property Lang::$'.$prop, E_USER_WARNING);
$dbt = debug_backtrace()[0];
$file = explode(DIRECTORY_SEPARATOR, $dbt['file']);
trigger_error('Lang - tried to use undefined property Lang::$'.$prop.', called in '.array_pop($file).':'.$dbt['line'], E_USER_WARNING);
return null;
}
@@ -79,7 +87,9 @@ class Lang
}
else if (!isset($var[$arg]))
{
trigger_error('Lang - undefined key "'.$arg.'" in property Lang::$'.$prop.'[\''.implode('\'][\'', $args).'\']', E_USER_WARNING);
$dbt = debug_backtrace()[0];
$file = explode(DIRECTORY_SEPARATOR, $dbt['file']);
trigger_error('Lang - undefined property Lang::$'.$prop.'[\''.implode('\'][\'', $args).'\'], called in '.array_pop($file).':'.$dbt['line'], E_USER_WARNING);
return null;
}
@@ -121,6 +131,66 @@ class Lang
return $b;
}
// truncate string after X chars. If X is inside a word truncate behind it.
public static function trimTextClean(string $text, int $len = 100) : string
{
// remove line breaks
$text = strtr($text, ["\n" => ' ', "\r" => ' ']);
// limit whitespaces to one at a time
$text = preg_replace('/\s+/', ' ', trim($text));
if ($len > 0 && mb_strlen($text) > $len)
{
$n = 0;
$b = [];
$parts = explode(' ', $text);
while ($n < $len && $parts)
{
$_ = array_shift($parts);
$n += mb_strlen($_);
$b[] = $_;
}
$text = implode(' ', $b).'…';
}
return $text;
}
// add line breaks to string after X chars. If X is inside a word break behind it.
public static function breakTextClean(string $text, int $len = 30, bool $asHTML = true) : string
{
// remove line breaks
$text = strtr($text, ["\n" => ' ', "\r" => ' ']);
// limit whitespaces to one at a time
$text = preg_replace('/\s+/', ' ', trim($text));
$row = [];
if ($len > 0 && mb_strlen($text) > $len)
{
$i = 0;
$n = 0;
$parts = explode(' ', $text);
foreach ($parts as $p)
{
$row[$i][] = $p;
$n += (mb_strlen($p) + 1);
if ($n < $len)
continue;
$n = 0;
$i++;
}
foreach ($row as &$r)
$r = implode(' ', $r);
}
return implode($asHTML ? '<br />' : '[br]', $row);
}
public static function sort($prop, $group, $method = SORT_NATURAL)
{
@@ -160,9 +230,10 @@ class Lang
return $tmp;
}
public static function getLocks($lockId, $interactive = false)
public static function getLocks(int $lockId, ?array &$ids = [], bool $interactive = false, bool $asHTML = false) : array
{
$locks = [];
$ids = [];
$lock = DB::Aowow()->selectRow('SELECT * FROM ?_lock WHERE id = ?d', $lockId);
if (!$lock)
return $locks;
@@ -173,47 +244,71 @@ class Lang
$rank = $lock['reqSkill'.$i];
$name = '';
if ($lock['type'.$i] == 1) // opened by item
if ($lock['type'.$i] == LOCK_TYPE_ITEM)
{
$name = ItemList::getName($prop);
if (!$name)
continue;
if ($interactive)
if ($interactive && $asHTML)
$name = '<a class="q1" href="?item='.$prop.'">'.$name.'</a>';
}
else if ($lock['type'.$i] == 2) // opened by skill
else if ($interactive && !$asHTML)
{
$name = '[item='.$prop.']';
$ids[Type::ITEM][] = $prop;
}
}
else if ($lock['type'.$i] == LOCK_TYPE_SKILL)
{
// exclude unusual stuff
if (!in_array($prop, [1, 2, 3, 4, 9, 16, 20]))
continue;
$name = self::spell('lockType', $prop);
if (!$name)
continue;
if ($interactive)
// skills
if (in_array($prop, [1, 2, 3, 20]))
{
$skill = 0;
switch ($prop)
{
case 1: $skill = 633; break; // Lockpicking
case 2: $skill = 182; break; // Herbing
case 3: $skill = 186; break; // Mining
case 20: $skill = 773; break; // Scribing
}
$skills = array(
1 => SKILL_LOCKPICKING,
2 => SKILL_HERBALISM,
3 => SKILL_MINING,
20 => SKILL_INSCRIPTION
);
if ($skill)
$name = '<a href="?skill='.$skill.'">'.$name.'</a>';
if ($interactive && $asHTML)
$name = '<a href="?skill='.$skills[$prop].'">'.$name.'</a>';
else if ($interactive && !$asHTML)
{
$name = '[skill='.$skills[$prop].']';
$ids[Type::SKILL][] = $skills[$prop];
}
if ($rank > 0)
$name .= ' ('.$rank.')';
}
// Lockpicking
else if ($prop == 4)
{
if ($interactive && $asHTML)
$name = '<a href="?spell=1842">'.$name.'</a>';
else if ($interactive && !$asHTML)
{
$name = '[spell=1842]';
$ids[Type::SPELL][] = 1842;
}
}
// exclude unusual stuff
else if (User::isInGroup(U_GROUP_STAFF))
{
if ($rank > 0)
$name .= ' ('.$rank.')';
}
else
continue;
}
else
continue;
$locks[$lock['type'.$i] == 1 ? $prop : -$prop] = sprintf(self::game('requires'), $name);
$locks[$lock['type'.$i] == LOCK_TYPE_ITEM ? $prop : -$prop] = $name;
}
return $locks;
@@ -252,7 +347,7 @@ class Lang
}
if ($class == ITEM_CLASS_MISC) // yeah hardcoded.. sue me!
return self::spell('cat', -5);
return self::spell('cat', -5, 0);
$tmp = [];
$strs = self::spell($class == ITEM_CLASS_ARMOR ? 'armorSubClass' : 'weaponSubClass');
@@ -307,7 +402,7 @@ class Lang
return implode(', ', $tmp);
}
public static function getClassString($classMask, &$ids = [], &$n = 0, $asHTML = true)
public static function getClassString(int $classMask, array &$ids = [], bool $asHTML = true) : string
{
$classMask &= CLASS_MASK_ALL; // clamp to available classes..
@@ -329,36 +424,26 @@ class Lang
$i++;
}
$n = count($tmp);
$ids = array_keys($tmp);
return implode(', ', $tmp);
}
public static function getRaceString($raceMask, &$side = 0, &$ids = [], &$n = 0, $asHTML = true)
public static function getRaceString(int $raceMask, array &$ids = [], bool $asHTML = true) : string
{
$raceMask &= RACE_MASK_ALL; // clamp to available races..
if ($raceMask == RACE_MASK_ALL) // available to all races (we don't display 'both factions')
return false;
if (!$raceMask)
return false;
$tmp = [];
$i = 1;
$base = $asHTML ? '<a href="?race=%d" class="q1">%s</a>' : '[race=%d]';
$br = $asHTML ? '' : '[br]';
if (!$raceMask)
{
$side |= SIDE_BOTH;
return self::game('ra', 0);
}
if ($raceMask & RACE_MASK_HORDE)
$side |= SIDE_HORDE;
if ($raceMask & RACE_MASK_ALLIANCE)
$side |= SIDE_ALLIANCE;
if ($raceMask == RACE_MASK_HORDE)
return self::game('ra', -2);
@@ -375,12 +460,22 @@ class Lang
$i++;
}
$n = count($tmp);
$ids = array_keys($tmp);
return implode(', ', $tmp);
}
public static function formatSkillBreakpoints(array $bp, bool $html = false) : string
{
$tmp = Lang::game('difficulty').Lang::main('colon');
for ($i = 0; $i < 4; $i++)
if (!empty($bp[$i]))
$tmp .= $html ? '<span class="r'.($i + 1).'">'.$bp[$i].'</span> ' : '[color=r'.($i + 1).']'.$bp[$i].'[/color] ';
return trim($tmp);
}
public static function nf($number, $decimals = 0, $no1k = false)
{
// [decimal, thousand]
@@ -388,6 +483,7 @@ class Lang
LOCALE_EN => [',', '.'],
LOCALE_FR => [' ', ','],
LOCALE_DE => ['.', ','],
LOCALE_CN => [',', '.'],
LOCALE_ES => ['.', ','],
LOCALE_RU => [' ', ',']
);
@@ -395,6 +491,12 @@ class Lang
return number_format($number, $decimals, $seps[User::$localeId][1], $no1k ? '' : $seps[User::$localeId][0]);
}
public static function typeName(int $type) : string
{
return Util::ucFirst(self::game(Type::getFileString($type)));
}
private static function vspf($var, $args)
{
if (is_array($var))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1945
localization/locale_zhcn.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ if (!defined('AOWOW_REVISION'))
class AccountPage extends GenericPage
{
protected $tpl = 'acc-dashboard';
protected $js = ['user.js', 'profile.js'];
protected $css = [['path' => 'Profiler.css']];
protected $js = [[JS_FILE, 'user.js'], [JS_FILE, 'profile.js']];
protected $css = [[CSS_FILE, 'Profiler.css']];
protected $mode = CACHE_TYPE_NONE;
protected $category = null;
protected $validCats = array(
@@ -27,13 +27,18 @@ class AccountPage extends GenericPage
protected $lvTabs = [];
protected $banned = [];
private $_post = array(
'username' => [FILTER_SANITIZE_SPECIAL_CHARS, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
'password' => [FILTER_UNSAFE_RAW, null],
'c_password' => [FILTER_UNSAFE_RAW, null],
'token' => [FILTER_UNSAFE_RAW, null],
'remember_me' => [FILTER_CALLBACK, ['options' => 'AccountPage::rememberCallback']],
'email' => [FILTER_SANITIZE_EMAIL, null]
protected $_get = array(
'token' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'next' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
);
protected $_post = array(
'username' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'password' => ['filter' => FILTER_UNSAFE_RAW],
'c_password' => ['filter' => FILTER_UNSAFE_RAW],
'token' => ['filter' => FILTER_UNSAFE_RAW],
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => 'AccountPage::rememberCallback'],
'email' => ['filter' => FILTER_SANITIZE_EMAIL]
);
public function __construct($pageCall, $pageParam)
@@ -43,9 +48,6 @@ class AccountPage extends GenericPage
parent::__construct($pageCall, $pageParam);
foreach ($this->_post as $k => &$v)
$v = !empty($_POST[$k]) ? filter_input(INPUT_POST, $k, $v[0], $v[1]) : null;
if ($pageParam)
{
// requires auth && not authed
@@ -57,7 +59,7 @@ class AccountPage extends GenericPage
}
}
private function rememberCallback($val)
protected static function rememberCallback($val)
{
return $val == 'yes' ? $val : null;
}
@@ -128,7 +130,7 @@ class AccountPage extends GenericPage
header('Location: '.$this->getNext(true), true, 302);
}
}
else if (!empty($_GET['token']) && ($_ = DB::Aowow()->selectCell('SELECT user FROM ?_account WHERE status IN (?a) AND token = ? AND statusTimer > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $_GET['token'])))
else if ($this->_get['token'] && ($_ = DB::Aowow()->selectCell('SELECT user FROM ?_account WHERE status IN (?a) AND token = ? AND statusTimer > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token'])))
$this->user = $_;
break;
@@ -156,13 +158,13 @@ class AccountPage extends GenericPage
$this->text = sprintf(Lang::account('createAccSent'), $this->_post['email']);
}
}
else if (!empty($_GET['token']) && ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE status = ?d AND token = ?', ACC_STATUS_NEW, $_GET['token'])))
else if ($this->_get['token'] && ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE status = ?d AND token = ?', ACC_STATUS_NEW, $this->_get['token'])))
{
$nStep = 2;
DB::Aowow()->query('UPDATE ?_account SET status = ?d, statusTimer = 0, token = 0, userGroups = ?d WHERE token = ?', ACC_STATUS_OK, U_GROUP_NONE, $_GET['token']);
DB::Aowow()->query('UPDATE ?_account SET status = ?d, statusTimer = 0, token = 0, userGroups = ?d WHERE token = ?', ACC_STATUS_OK, U_GROUP_NONE, $this->_get['token']);
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, ?d + 1, UNIX_TIMESTAMP() + ?d)', User::$ip, CFG_ACC_FAILED_AUTH_COUNT, CFG_ACC_FAILED_AUTH_BLOCK);
$this->text = sprintf(Lang::account('accActivated'), $_GET['token']);
$this->text = sprintf(Lang::account('accActivated'), $this->_get['token']);
}
else
$this->next = $this->getNext();
@@ -313,17 +315,17 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
$this->text = sprintf(Lang::account('recovPassSent'), $this->_post['email']);
}
}
else if (isset($_GET['token'])) // step 2
else if ($this->_get['token']) // step 2
{
$step = 2;
$this->resetPass = true;
$this->token = $_GET['token'];
$this->token = $this->_get['token'];
}
else if ($this->_post['token'] && $this->_post['email'] && $this->_post['password'] && $this->_post['c_password'])
{
$step = 2;
$this->resetPass = true;
$this->token = $_GET['token']; // insecure source .. that sucks; but whats the worst that could happen .. this account cannot be recovered for some minutes
$this->token = $this->_post['token']; // insecure source .. that sucks; but whats the worst that could happen .. this account cannot be recovered for some minutes
if ($err = $this->doResetPass())
$this->error = $err;
@@ -413,7 +415,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
}
// username taken
if ($_ = DB::Aowow()->SelectCell('SELECT user FROM ?_account WHERE (user = ? OR email = ?) AND (status <> ?d OR (status = ?d AND statusTimer > UNIX_TIMESTAMP()))', $this->_post['username'], $email, ACC_STATUS_NEW, ACC_STATUS_NEW))
if ($_ = DB::Aowow()->SelectCell('SELECT user FROM ?_account WHERE (user = ? OR email = ?) AND (status <> ?d OR (status = ?d AND statusTimer > UNIX_TIMESTAMP()))', $this->_post['username'], $this->_post['email'], ACC_STATUS_NEW, ACC_STATUS_NEW))
return $_ == $this->_post['username'] ? Lang::account('nameInUse') : Lang::account('mailInUse');
// create..
@@ -433,7 +435,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
);
if (!$ok)
return Lang::main('intError');
else if ($_ = $this->sendMail(Lang::mail('accConfirm', 0), sprintf(Lang::mail('accConfirm', 1), $token), CFG_ACC_CREATE_SAVE_DECAY))
else if ($_ = $this->sendMail(Lang::user('accConfirm', 0), sprintf(Lang::user('accConfirm', 1), $token), CFG_ACC_CREATE_SAVE_DECAY))
{
if ($id = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE token = ?', $token))
Util::gainSiteReputation($id, SITEREP_ACTION_REGISTER);
@@ -454,7 +456,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
return $_;
// send recovery mail
return $this->sendMail(Lang::mail('resetPass', 0), sprintf(Lang::mail('resetPass', 1), $token), CFG_ACC_RECOVERY_DECAY);
return $this->sendMail(Lang::user('resetPass', 0), sprintf(Lang::user('resetPass', 1), $token), CFG_ACC_RECOVERY_DECAY);
}
private function doResetPass()
@@ -473,10 +475,10 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
if (!$uId)
return Lang::account('emailNotFound'); // assume they didn't meddle with the token
if (!User::verifyCrypt($newPass))
if (!User::verifyCrypt($this->_post['c_password']))
return Lang::account('newPassDiff');
if (!DB::Aowow()->query('UPDATE ?_account SET passHash = ?, status = ?d WHERE id = ?d', User::hashcrypt($newPass), ACC_STATUS_OK, $uId))
if (!DB::Aowow()->query('UPDATE ?_account SET passHash = ?, status = ?d WHERE id = ?d', User::hashCrypt($this->_post['c_password']), ACC_STATUS_OK, $uId))
return Lang::main('intError');
}
@@ -486,7 +488,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
return $_;
// send recovery mail
return $this->sendMail(Lang::mail('recoverUser', 0), sprintf(Lang::mail('recoverUser', 1), $token), CFG_ACC_RECOVERY_DECAY);
return $this->sendMail(Lang::user('recoverUser', 0), sprintf(Lang::user('recoverUser', 1), $token), CFG_ACC_RECOVERY_DECAY);
}
private function initRecovery($type, $delay, &$token)
@@ -508,7 +510,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
{
// send recovery mail
$subj = CFG_NAME_SHORT.Lang::main('colon') . $subj;
$msg .= "\r\n\r\n".sprintf(Lang::mail('tokenExpires'), Util::formatTime($delay * 1000))."\r\n";
$msg .= "\r\n\r\n".sprintf(Lang::user('tokenExpires'), Util::formatTime($delay * 1000))."\r\n";
$header = 'From: '.CFG_CONTACT_EMAIL . "\r\n" .
'Reply-To: '.CFG_CONTACT_EMAIL . "\r\n" .
'X-Mailer: PHP/' . phpversion();
@@ -520,8 +522,8 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
private function getNext($forHeader = false)
{
$next = $forHeader ? '.' : '';
if (isset($_GET['next']))
$next = $_GET['next'];
if ($this->_get['next'])
$next = $this->_get['next'];
else if (isset($_SERVER['HTTP_REFERER']) && strstr($_SERVER['HTTP_REFERER'], '?'))
$next = explode('?', $_SERVER['HTTP_REFERER'])[1];

View File

@@ -23,28 +23,32 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class AchievementPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_ACHIEVEMENT;
protected $type = Type::ACHIEVEMENT;
protected $typeId = 0;
protected $tpl = 'achievement';
protected $path = [0, 9];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $_get = ['domain' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkDomain']];
private $powerTpl = '$WowheadPower.registerAchievement(%d, %d, %s);';
public function __construct($pageCall, $id)
{
parent::__construct($pageCall, $id);
// temp locale
if ($this->mode == CACHE_TYPE_TOOLTIP && isset($_GET['domain']))
Util::powerUseLocale($_GET['domain']);
if ($this->mode == CACHE_TYPE_TOOLTIP && $this->_get['domain'])
Util::powerUseLocale($this->_get['domain']);
$this->typeId = intVal($id);
$this->subject = new AchievementList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound();
$this->notFound(Lang::game('achievement'), Lang::achievement('notFound'));
$this->extendGlobalData($this->subject->getJSGlobals(GLOBALINFO_REWARDS));
@@ -58,7 +62,7 @@ class AchievementPage extends GenericPage
do
{
array_unshift($this->path, $curCat);
$curCat = DB::Aowow()->SelectCell('SELECT parentCategory FROM ?_achievementcategory WHERE id = ?d', $curCat);
$curCat = DB::Aowow()->SelectCell('SELECT parentCat FROM ?_achievementcategory WHERE id = ?d', $curCat);
}
while ($curCat > 0);
@@ -99,6 +103,13 @@ class AchievementPage extends GenericPage
$infobox[] = Lang::main('side').Lang::main('colon').Lang::game('si', SIDE_BOTH);
}
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
// realm first available?
if ($this->subject->getField('flags') & 0x100 && DB::isConnectable(DB_AUTH))
{
@@ -129,7 +140,7 @@ class AchievementPage extends GenericPage
$series[$pos][] = array(
'side' => $chainAcv->getField('faction'),
'typeStr' => Util::$typeStrings[TYPE_ACHIEVEMENT],
'typeStr' => Type::getFileString(Type::ACHIEVEMENT),
'typeId' => $aId,
'name' => $chainAcv->getField('name', true)
);
@@ -143,13 +154,13 @@ class AchievementPage extends GenericPage
$this->mail = $this->createMail($reqBook);
$this->headIcons = [$this->subject->getField('iconString')];
$this->infobox = $infobox ? '[ul][li]'.implode('[/li][li]', $infobox).'[/li][/ul]' : null;
$this->series = $series ? [[$series, null]] : null;
$this->series = $series ? [[array_values($series), null]] : null;
$this->description = $this->subject->getField('description', true);
$this->redButtons = array(
BUTTON_WOWHEAD => !($this->subject->getField('cuFlags') & CUSTOM_SERVERSIDE),
BUTTON_LINKS => array(
'linkColor' => 'ffffff00',
'linkId' => Util::$typeStrings[TYPE_ACHIEVEMENT].':'.$this->typeId.':&quot;..UnitGUID(&quot;player&quot;)..&quot;:0:0:0:0:0:0:0:0',
'linkId' => Type::getFileString(Type::ACHIEVEMENT).':'.$this->typeId.':&quot;..UnitGUID(&quot;player&quot;)..&quot;:0:0:0:0:0:0:0:0',
'linkName' => $this->name,
'type' => $this->type,
'typeId' => $this->typeId
@@ -162,13 +173,13 @@ class AchievementPage extends GenericPage
);
if ($reqBook)
$this->addCss(['path' => 'Book.css']);
$this->addScript([CSS_FILE, 'Book.css']);
// create rewards
if ($foo = $this->subject->getField('rewards'))
{
array_walk($foo, function(&$item) {
$item = $item[0] != TYPE_ITEM ? null : $item[1];
$item = $item[0] != Type::ITEM ? null : $item[1];
});
$bar = new ItemList(array(['i.id', $foo]));
@@ -177,9 +188,9 @@ class AchievementPage extends GenericPage
$this->rewards['item'][] = array(
'name' => $bar->getField('name', true),
'quality' => $bar->getField('quality'),
'typeStr' => Util::$typeStrings[TYPE_ITEM],
'typeStr' => Type::getFileString(Type::ITEM),
'id' => $id,
'globalStr' => 'g_items'
'globalStr' => Type::getJSGlobalString(Type::ITEM)
);
}
}
@@ -187,7 +198,7 @@ class AchievementPage extends GenericPage
if ($foo = $this->subject->getField('rewards'))
{
array_walk($foo, function(&$item) {
$item = $item[0] != TYPE_TITLE ? null : $item[1];
$item = $item[0] != Type::TITLE ? null : $item[1];
});
$bar = new TitleList(array(['id', $foo]));
@@ -259,9 +270,13 @@ class AchievementPage extends GenericPage
$scripts = [];
// serverside extra-Data
$crtIds = array_column($this->subject->getCriteria(), 'id');
if ($crtIds = array_column($this->subject->getCriteria(), 'id'))
{
Util::checkNumeric($crtIds);
$crtExtraData = DB::World()->select('SELECT criteria_id AS ARRAY_KEY, type AS ARRAY_KEY2, value1, value2, ScriptName FROM achievement_criteria_data WHERE criteria_id IN (?a)', $crtIds);
}
else
$crtExtraData = [];
foreach ($this->subject->getCriteria() as $i => $crt)
{
@@ -351,10 +366,10 @@ class AchievementPage extends GenericPage
$tmp['icon'] = $iconId;
$this->criteria['icons'][] = array(
'itr' => $iconId++,
'type' => 'g_achievements',
'type' => Type::getJSGlobalString(Type::ACHIEVEMENT),
'id' => $obj,
);
$this->extendGlobalIds(TYPE_ACHIEVEMENT, $obj);
$this->extendGlobalIds(Type::ACHIEVEMENT, $obj);
break;
// link to quest
case ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST:
@@ -374,11 +389,11 @@ class AchievementPage extends GenericPage
'href' => '?spell='.$obj,
'text' => ($crtName ?: SpellList::getName($obj))
);
$this->extendGlobalIds(TYPE_SPELL, $obj);
$this->extendGlobalIds(Type::SPELL, $obj);
$tmp['icon'] = $iconId;
$this->criteria['icons'][] = array(
'itr' => $iconId++,
'type' => 'g_spells',
'type' => Type::getJSGlobalString(Type::SPELL),
'id' => $obj,
);
break;
@@ -398,7 +413,7 @@ class AchievementPage extends GenericPage
$tmp['icon'] = $iconId;
$this->criteria['icons'][] = array(
'itr' => $iconId++,
'type' => 'g_items',
'type' => Type::getJSGlobalString(Type::ITEM),
'id' => $obj,
'count' => $qty,
);
@@ -516,43 +531,17 @@ class AchievementPage extends GenericPage
}
}
protected function generateTooltip($asError = false)
protected function generateTooltip()
{
if ($asError)
return '$WowheadPower.registerAchievement('.$this->typeId.', '.User::$localeId.', {});';
$x = '$WowheadPower.registerAchievement('.$this->typeId.', '.User::$localeId.",{\n";
$x .= "\tname_".User::$localeString.": '".Util::jsEscape($this->subject->getField('name', true))."',\n";
$x .= "\ticon: '".rawurlencode($this->subject->getField('iconString', true, true))."',\n";
$x .= "\ttooltip_".User::$localeString.": '".$this->subject->renderTooltip()."'\n";
$x .= "});";
return $x;
$power = new StdClass();
if (!$this->subject->error)
{
$power->{'name_'.User::$localeString} = $this->subject->getField('name', true);
$power->icon = rawurlencode($this->subject->getField('iconString', true, true));
$power->{'tooltip_'.User::$localeString} = $this->subject->renderTooltip();
}
public function display($override = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::display($override);
if (!$this->loadCache($tt))
{
$tt = $this->generateTooltip();
$this->saveCache($tt);
}
header('Content-type: application/x-javascript; charset=utf-8');
die($tt);
}
public function notFound($title = '', $msg = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::notFound($title ?: Lang::game('achievement'), $msg ?: Lang::achievement('notFound'));
header('Content-type: application/x-javascript; charset=utf-8');
echo $this->generateTooltip(true);
exit();
return sprintf($this->powerTpl, $this->typeId, User::$localeId, Util::toJSON($power, JSON_AOWOW_POWER));
}
private function createMail(&$reqCss = false)
@@ -561,14 +550,16 @@ class AchievementPage extends GenericPage
if ($_ = $this->subject->getField('mailTemplate'))
{
$letter = DB::Aowow()->selectRow('SELECT * FROM ?_mailtemplate WHERE id = ?d', $_);
$letter = DB::Aowow()->selectRow('SELECT * FROM ?_mails WHERE id = ?d', $_);
if (!$letter)
return [];
$reqCss = true;
$mail = array(
'id' => $_,
'delay' => null,
'sender' => null,
'attachments' => [],
'subject' => Util::parseHtmlText(Util::localizedString($letter, 'subject', true)),
'text' => Util::parseHtmlText(Util::localizedString($letter, 'text', true))
);
@@ -577,15 +568,17 @@ class AchievementPage extends GenericPage
{
$reqCss = true;
$mail = array(
'id' => -$this->typeId,
'delay' => null,
'sender' => null,
'attachments' => [],
'subject' => Util::parseHtmlText($this->subject->getField('subject', true, true)),
'text' => $_
);
}
if ($_ = CreatureList::getName($this->subject->getField('sender')))
$mail['sender'] = sprintf(Lang::quest('mailBy'), $this->subject->getField('sender'), $_);
$mail['sender'] = sprintf(Lang::mail('mailBy'), $this->subject->getField('sender'), $_);
return $mail;
}

View File

@@ -8,14 +8,17 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class AchievementsPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_ACHIEVEMENT;
protected $type = Type::ACHIEVEMENT;
protected $tpl = 'achievements';
protected $path = [0, 9];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $js = ['filters.js'];
protected $js = [[JS_FILE, 'filters.js']];
protected $_get = ['filter' => ['filter' => FILTER_UNSAFE_RAW]];
protected $validCats = array(
92 => true,
96 => [14861, 14862, 14863],
@@ -64,7 +67,7 @@ class AchievementsPage extends GenericPage
// recreate form selection
$this->filter = $this->filterObj->getForm();
$this->filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : null;
$this->filter['query'] = $this->_get['filter'];
$this->filter['initData'] = ['init' => 'achievements'];
if ($x = $this->filterObj->getSetCriteria())
@@ -76,16 +79,11 @@ class AchievementsPage extends GenericPage
$acvList = new AchievementList($conditions);
if (!$acvList->getMatches())
{
$curCats = $catList = [!empty($this->category) ? (int)end($this->category) : 0];
while ($curCats)
{
$curCats = DB::Aowow()->SelectCol('SELECT Id FROM ?_achievementcategory WHERE parentCategory IN (?a)', $curCats);
$catList = array_merge($catList, $curCats);
}
$category = [!empty($this->category) ? (int)end($this->category) : 0];
$conditions = [];
if ($fiCnd)
$conditions[] = $fiCnd;
if ($catList)
if ($catList = DB::Aowow()->SelectCol('SELECT Id FROM ?_achievementcategory WHERE parentCat IN (?a) OR parentCat2 IN (?a) ', $category, $category))
$conditions[] = ['category', $catList];
$acvList = new AchievementList($conditions);
@@ -127,21 +125,15 @@ class AchievementsPage extends GenericPage
{
array_unshift($this->title, Util::ucFirst(Lang::game('achievements')));
if ($this->category)
{
$catrow = DB::Aowow()->SelectRow('SELECT * FROM ?_achievementcategory WHERE id = ?d', end($this->category));
array_unshift($this->title, Util::localizedString($catrow, 'name'));
}
array_unshift($this->title, Lang::achievement('cat', end($this->category)));
}
protected function generatePath()
{
if ($this->category)
{
$catrows = DB::Aowow()->SelectCol('SELECT id FROM ?_achievementcategory WHERE id IN (?a)', $this->category);
foreach ($catrows as $cat)
foreach ($this->category as $cat)
$this->path[] = $cat;
}
}
}
?>

View File

@@ -6,13 +6,19 @@ if (!defined('AOWOW_REVISION'))
class AdminPage extends GenericPage
{
protected $tpl = null; // depends on the subject
protected $reqUGroup = U_GROUP_NONE; // actual group dependant on the subPage
protected $reqAuth = true;
protected $path = [4];
protected $tabId = 4;
protected $_get = array(
'all' => ['filter' => FILTER_UNSAFE_RAW],
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'urldecode'],
);
private $generator = '';
public function __construct($pageCall, $pageParam)
@@ -51,13 +57,21 @@ class AdminPage extends GenericPage
array_push($this->path, 2, 16);
$this->name = 'Weight Presets';
break;
case 'guides':
$this->reqUGroup = U_GROUP_STAFF;
$this->generator = 'handleGuideApprove';
$this->tpl = 'list-page-generic';
array_push($this->path, 1, 25);
$this->name = 'Pending Guides';
break;
default: // error out through unset template
}
parent::__construct($pageCall, $pageParam);
}
protected function generateContent()
protected function generateContent() : void
{
if (!$this->generator || function_exists($this->generator))
return;
@@ -65,17 +79,17 @@ class AdminPage extends GenericPage
$this->{$this->generator}();
}
private function handleConfig()
private function handleConfig() : void
{
$this->addCSS(array(
['string' => '.grid input[type=\'text\'], .grid input[type=\'number\'] { width:250px; text-align:left; }'],
['string' => '.grid input[type=\'button\'] { width:65px; padding:2px; }'],
['string' => '.grid a.tip { margin:0px 5px; opacity:0.8; }'],
['string' => '.grid a.tip:hover { opacity:1; }'],
['string' => '.grid tr { height:30px; }'],
['string' => '.grid .disabled { opacity:0.4 !important; }'],
['string' => '.grid .status { position:absolute; right:5px; }'],
));
$this->addScript(
[CSS_STRING, '.grid input[type=\'text\'], .grid input[type=\'number\'] { width:250px; text-align:left; }'],
[CSS_STRING, '.grid input[type=\'button\'] { width:65px; padding:2px; }'],
[CSS_STRING, '.grid a.tip { margin:0px 5px; opacity:0.8; }'],
[CSS_STRING, '.grid a.tip:hover { opacity:1; }'],
[CSS_STRING, '.grid tr { height:30px; }'],
[CSS_STRING, '.grid .disabled { opacity:0.4 !important; }'],
[CSS_STRING, '.grid .status { position:absolute; right:5px; }'],
);
$head = '<table class="grid"><tr><th><b>Key</b></th><th><b>Value</b></th><th style="width:150px;"><b>Options</b></th></tr>';
$mainTab = [];
@@ -115,10 +129,10 @@ class AdminPage extends GenericPage
)];
}
private function handlePhpInfo()
private function handlePhpInfo() : void
{
$this->addCSS([
'string' => "\npre {margin: 0px; font-family: monospace;}\n" .
$this->addScript([
CSS_STRING, "\npre {margin: 0px; font-family: monospace;}\n" .
"td, th { border: 1px solid #000000; vertical-align: baseline;}\n" .
".p {text-align: left;}\n" .
".e {background-color: #ccccff; font-weight: bold; color: #000000;}\n" .
@@ -179,30 +193,29 @@ class AdminPage extends GenericPage
}
}
private function handleScreenshots()
private function handleScreenshots() : void
{
$this->addJS('screenshot.js');
$this->addCSS(array(
['string' => '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'],
['string' => '#highlightedRow { background-color: #322C1C; }']
));
$this->addScript(
[JS_FILE, 'screenshot.js'],
[CSS_STRING, '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'],
[CSS_STRING, '#highlightedRow { background-color: #322C1C; }'],
);
$ssGetAll = isset($_GET['all']) && empty($_GET['all']);
$ssGetAll = $this->_get['all'];
$ssPages = [];
$ssData = [];
$nMatches = 0;
if (!empty($_GET['type']) && !empty($_GET['typeid']))
if ($this->_get['type'] && $this->_get['typeId'])
{
$ssData = CommunityContent::getScreenshotsForManager(intVal($_GET['type']), intVal($_GET['typeid']));
$ssData = CommunityContent::getScreenshotsForManager($this->_get['type'], $this->_get['typeid']);
$nMatches = count($ssData);
}
else if (!empty($_GET['user']))
else if ($this->_get['user'])
{
$name = urldecode($_GET['user']);
if (mb_strlen($name) >= 3)
if (mb_strlen($this->_get['user']) >= 3)
{
if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', ucFirst($name)))
if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', ucFirst($this->_get['user'])))
{
$ssData = CommunityContent::getScreenshotsForManager(0, 0, $uId);
$nMatches = count($ssData);
@@ -218,10 +231,12 @@ class AdminPage extends GenericPage
$this->ssNFound = $nMatches; // ssm_numPagesFound
}
private function handleWeightPresets()
private function handleWeightPresets() : void
{
$this->addCSS(['string' => '.wt-edit {display:inline-block; vertical-align:top; width:350px;}']);
$this->addJS('filters.js');
$this->addScript(
[JS_FILE, 'filters.js'],
[CSS_STRING, '.wt-edit {display:inline-block; vertical-align:top; width:350px;}'],
);
$head = $body = '';
@@ -245,6 +260,26 @@ class AdminPage extends GenericPage
$this->extraHTML = '<script type="text/javascript">var wt_presets = '.Util::toJSON($weights).";</script>\n\n";
}
private function handleGuideApprove() : void
{
$pending = new GuideList([['status', GUIDE_STATUS_REVIEW]]);
if ($pending->error)
$data = [];
else
{
$data = $pending->getListviewData();
$latest = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, MAX(`rev`) FROM ?_articles WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `rev`', Type::GUIDE, $pending->getFoundIDs());
foreach ($latest as $id => $rev)
$data[$id]['rev'] = $rev;
}
$this->lvTabs[] = ['guide', array(
'data' => array_values($data),
'hiddenCols' => ['patch', 'comments', 'views', 'rating'],
'extraCols' => '$_'
), 'guideAdminCol'];
}
private function configAddRow($r)
{
$buff = '<tr>';

146
pages/areatrigger.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// menuId 102: Areatrigger g_initPath()
// tabid 0: Database g_initHeader()
class AreaTriggerPage extends GenericPage
{
use TrDetailPage;
protected $type = Type::AREATRIGGER;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 102];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $reqUGroup = U_GROUP_STAFF;
public function __construct($pageCall, $id)
{
$this->contribute = CONTRIBUTE_NONE;
parent::__construct($pageCall, $id);
$this->typeId = intVal($id);
$this->subject = new AreaTriggerList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound(Lang::game('areatrigger'), Lang::areatrigger('notFound'));
$this->name = $this->subject->getField('name') ?: 'AT #'.$this->typeId;
}
protected function generatePath()
{
$this->path[] = $this->subject->getField('type');
}
protected function generateTitle()
{
array_unshift($this->title, $this->name, Util::ucFirst(Lang::game('areatrigger')));
}
protected function generateContent()
{
$this->addScript([JS_FILE, '?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$_type = $this->subject->getField('type');
/****************/
/* Main Content */
/****************/
// get spawns
$map = null;
if ($spawns = $this->subject->getSpawns(SPAWNINFO_FULL))
{
$ta = $this->subject->getField('teleportA');
$tf = $this->subject->getField('teleportF');
$tx = $this->subject->getField('teleportX');
$ty = $this->subject->getField('teleportY');
$to = $this->subject->getField('teleportO');
// add teleport target
if ($ta && $tx && $ty)
{
$o = Util::O2Deg($to);
$endPoint = array($tx, $ty, array(
'type' => 4,
'tooltip' => array(
'Teleport Destination' => array(
'info' => ['Orientation'.Lang::main('colon').$o[0].'° ('.$o[1].')']
)
)
));
if (isset($spawns[$ta][$tf]))
$spawns[$ta][$tf]['coords'][] = $endPoint;
else
$spawns[$ta][$tf]['coords'] = [$endPoint];
}
$map = array(
'data' => ['parent' => 'mapper-generic'],
'mapperData' => &$spawns
);
foreach ($spawns as $areaId => &$areaData)
$map['extra'][$areaId] = ZoneList::getName($areaId);
}
// smart AI
$sai = null;
if ($_type == AT_TYPE_SMART)
{
$sai = new SmartAI(SAI_SRC_TYPE_AREATRIGGER, $this->typeId, ['name' => $this->name, 'teleportA' => $this->subject->getField('teleportA')]);
if ($sai->prepare())
$this->extendGlobalData($sai->getJSGlobals());
}
$this->map = $map;
$this->infobox = false;
$this->smartAI = $sai ? $sai->getMarkdown() : null;
$this->redButtons = array(
BUTTON_LINKS => false,
BUTTON_WOWHEAD => false
);
/**************/
/* Extra Tabs */
/**************/
if ($_type == AT_TYPE_OBJECTIVE)
{
$relQuest = new QuestList(array(['id', $this->subject->getField('quest')]));
if (!$relQuest->error)
{
$this->extendGlobalData($relQuest->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_REWARDS));
$this->lvTabs[] = ['quest', ['data' => array_values($relQuest->getListviewData())]];
}
}
else if ($_type == AT_TYPE_TELEPORT)
{
$relZone = new ZoneList(array(['id', $this->subject->getField('teleportA')]));
if (!$relZone->error)
{
$this->lvTabs[] = ['zone', ['data' => array_values($relZone->getListviewData())]];
}
}
else if ($_type == AT_TYPE_SCRIPT)
{
$relTrigger = new AreaTriggerList(array(['id', $this->typeId, '!'], ['name', $this->subject->getField('name')]));
if (!$relTrigger->error)
{
$this->lvTabs[] = ['areatrigger', ['data' => array_values($relTrigger->getListviewData()), 'name' => Util::ucFirst(Lang::game('areatrigger'))], 'areatrigger'];
}
}
}
}
?>

89
pages/areatriggers.php Normal file
View File

@@ -0,0 +1,89 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// menuId 102: Areatrigger g_initPath()
// tabid 0: Database g_initHeader()
class AreaTriggersPage extends GenericPage
{
use TrListPage;
protected $type = Type::AREATRIGGER;
protected $tpl = 'areatriggers';
protected $path = [0, 102];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $validCats = [0, 1, 2, 3, 4, 5];
protected $js = [[JS_FILE, 'filters.js']];
protected $reqUGroup = U_GROUP_STAFF;
protected $_get = ['filter' => ['filter' => FILTER_UNSAFE_RAW]];
public function __construct($pageCall, $pageParam)
{
$this->getCategoryFromUrl($pageParam);;
if (isset($this->category[0]))
header('Location: ?areatriggers&filter=ty='.$this->category[0], true, 302);
$this->filterObj = new AreaTriggerListFilter();
parent::__construct($pageCall, $pageParam);
$this->name = Util::ucFirst(Lang::game('areatriggers'));
}
protected function generateContent()
{
// recreate form selection
$this->filter = $this->filterObj->getForm();
$this->filter['query'] = $this->_get['filter'];
$this->filter['initData'] = ['init' => 'areatrigger'];
if ($x = $this->filterObj->getSetCriteria())
$this->filter['initData']['sc'] = $x;
$conditions = [];
if ($_ = $this->filterObj->getConditions())
$conditions[] = $_;
$tabData = [];
$trigger = new AreaTriggerList($conditions);
if (!$trigger->error)
{
$tabData['data'] = array_values($trigger->getListviewData());
// create note if search limit was exceeded; overwriting 'note' is intentional
if ($trigger->getMatches() > CFG_SQL_LIMIT_DEFAULT)
{
$tabData['note'] = sprintf(Util::$tryFilteringEntityString, $trigger->getMatches(), '"'.Lang::game('areatriggers').'"', CFG_SQL_LIMIT_DEFAULT);
$tabData['_truncated'] = 1;
}
if ($this->filterObj->error)
$tabData['_errors'] = 1;
}
$this->lvTabs[] = ['areatrigger', $tabData, 'areatrigger'];
}
protected function generateTitle()
{
array_unshift($this->title, $this->name);
$form = $this->filterObj->getForm();
if (isset($form['ty']) && count($form['ty']) == 1)
array_unshift($this->title, Lang::areatrigger('types', $form['ty'][0]));
}
protected function generatePath()
{
$form = $this->filterObj->getForm();
if (isset($form['ty']) && count($form['ty']) == 1)
$this->path[] = $form['ty'];
}
}
?>

View File

@@ -12,14 +12,19 @@ class ArenaTeamPage extends GenericPage
protected $lvTabs = [];
protected $type = Type::ARENA_TEAM;
protected $tabId = 1;
protected $path = [1, 5, 3];
protected $tpl = 'roster';
protected $js = ['profile_all.js', 'profile.js'];
protected $css = [['path' => 'Profiler.css']];
protected $js = [[JS_FILE, 'profile_all.js'], [JS_FILE, 'profile.js']];
protected $css = [[CSS_FILE, 'Profiler.css']];
public function __construct($pageCall, $pageParam)
{
if (!CFG_PROFILER_ENABLE)
$this->error();
$params = array_map('urldecode', explode('.', $pageParam));
if ($params[0])
$params[0] = Profiler::urlize($params[0]);
@@ -92,7 +97,7 @@ class ArenaTeamPage extends GenericPage
if ($this->doResync)
return;
$this->addJS('?data=realms.weight-presets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=realms.weight-presets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$this->redButtons[BUTTON_RESYNC] = [$this->subjectGUID, 'arena-team'];
@@ -121,15 +126,15 @@ class ArenaTeamPage extends GenericPage
}
}
public function notFound($title = '', $msg = '')
public function notFound(string $title = '', string $msg = '') : void
{
return parent::notFound($title ?: Util::ucFirst(Lang::profiler('profiler')), $msg ?: Lang::profiler('notFound', 'arenateam'));
parent::notFound($title ?: Util::ucFirst(Lang::profiler('profiler')), $msg ?: Lang::profiler('notFound', 'arenateam'));
}
private function handleIncompleteData($teamGuid)
{
//display empty page and queue status
$newId = Profiler::scheduleResync(TYPE_ARENA_TEAM, $this->realmId, $teamGuid);
$newId = Profiler::scheduleResync(Type::ARENA_TEAM, $this->realmId, $teamGuid);
$this->doResync = ['arena-team', $newId];
$this->initialSync();

View File

@@ -10,13 +10,20 @@ class ArenaTeamsPage extends GenericPage
{
use TrProfiler;
protected $type = Type::ARENA_TEAM;
protected $tabId = 1;
protected $path = [1, 5, 3];
protected $tpl = 'arena-teams';
protected $js = ['filters.js', 'profile_all.js', 'profile.js'];
protected $js = [[JS_FILE, 'filters.js'], [JS_FILE, 'profile_all.js'], [JS_FILE, 'profile.js']];
protected $_get = ['filter' => ['filter' => FILTER_UNSAFE_RAW]];
public function __construct($pageCall, $pageParam)
{
if (!CFG_PROFILER_ENABLE)
$this->error();
$this->getSubjectFromUrl($pageParam);
$this->filterObj = new ArenaTeamListFilter();
@@ -50,7 +57,7 @@ class ArenaTeamsPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=realms&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=realms&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$conditions = [];
if (!User::isInGroup(U_GROUP_EMPLOYEE))
@@ -61,7 +68,7 @@ class ArenaTeamsPage extends GenericPage
// recreate form selection
$this->filter = $this->filterObj->getForm();
$this->filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : null;
$this->filter['query'] = $this->_get['filter'];
$this->filter['initData'] = ['type' => 'arenateams'];
$tabData = array(

View File

@@ -8,15 +8,15 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class ClassPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_CLASS;
protected $type = Type::CHR_CLASS;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 12];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $js = ['swfobject.js'];
protected $js = [[JS_FILE, 'swfobject.js']];
public function __construct($pageCall, $id)
{
@@ -43,7 +43,7 @@ class ClassPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$infobox = Lang::getInfoBoxForFlags($this->subject->getField('cuFlags'));
$_mask = 1 << ($this->typeId - 1);
@@ -213,7 +213,7 @@ class ClassPage extends GenericPage
$conditions = array(
['npcflag', 0x30, '&'], // is trainer
['trainerType', 0], // trains class spells
['trainerClass', $this->typeId]
['trainerRequirement', $this->typeId]
);
$trainer = new CreatureList($conditions);

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class ClassesPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_CLASS;
protected $type = Type::CHR_CLASS;
protected $tpl = 'list-page-generic';
protected $path = [0, 12];
protected $tabId = 0;

View File

@@ -12,28 +12,31 @@ class ComparePage extends GenericPage
protected $path = [1, 3];
protected $mode = CACHE_TYPE_NONE;
protected $js = array(
'profile.js',
'Draggable.js',
'filters.js',
'Summary.js',
'swfobject.js',
[JS_FILE, 'profile.js'],
[JS_FILE, 'Draggable.js'],
[JS_FILE, 'filters.js'],
[JS_FILE, 'Summary.js'],
[JS_FILE, 'swfobject.js'],
);
protected $css = [['path' => 'Summary.css']];
protected $css = [[CSS_FILE, 'Summary.css']];
protected $summary = [];
protected $cmpItems = [];
protected $_get = ['compare' => ['filter' => FILTER_CALLBACK, 'options' => 'ComparePage::checkCompareString']];
protected $_cookie = ['compare_groups' => ['filter' => FILTER_CALLBACK, 'options' => 'ComparePage::checkCompareString']];
private $compareString = '';
public function __construct($pageCall, $__)
{
parent::__construct($pageCall, $__);
// prefer $_GET over $_COOKIE
if (!empty($_GET['compare']))
$this->compareString = $_GET['compare'];
else if (!empty($_COOKIE['compare_groups']))
$this->compareString = urldecode($_COOKIE['compare_groups']);
// prefer GET over COOKIE
if ($this->_get['compare'])
$this->compareString = $this->_get['compare'];
else if ($this->_cookie['compare_groups'])
$this->compareString = $this->_cookie['compare_groups'];
$this->name = Lang::main('compareTool');
}
@@ -41,7 +44,7 @@ class ComparePage extends GenericPage
protected function generateContent()
{
// add conditional js
$this->addJS('?data=weight-presets.gems.enchants.itemsets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=weight-presets.gems.enchants.itemsets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$this->summary = array(
'template' => 'compare',
@@ -56,14 +59,12 @@ class ComparePage extends GenericPage
$items = $outSet = [];
foreach ($sets as $set)
{
$itemSting = explode(':', $set);
$itemString = explode(':', $set);
$outString = [];
foreach ($itemSting as $substring)
foreach ($itemString as $is)
{
$params = explode('.', $substring);
$params = array_pad(explode('.', $is), 7, 0);
$items[] = (int)$params[0];
while (sizeof($params) < 7)
$params[] = 0;
$outString[] = $params;
}
@@ -100,6 +101,15 @@ class ComparePage extends GenericPage
}
protected function generatePath() {}
protected static function checkCompareString(string $val) : string
{
$val = urldecode($val);
if (preg_match('/[^\d\.:;]/', $val))
return '';
return $val;
}
}
?>

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class CurrenciesPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_CURRENCY;
protected $type = Type::CURRENCY;
protected $tpl = 'list-page-generic';
protected $path = [0, 15];
protected $tabId = 0;

View File

@@ -8,28 +8,32 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class CurrencyPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_CURRENCY;
protected $type = Type::CURRENCY;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 15];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $_get = ['domain' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkDomain']];
private $powerTpl = '$WowheadPower.registerCurrency(%d, %d, %s);';
public function __construct($pageCall, $id)
{
parent::__construct($pageCall, $id);
// temp locale
if ($this->mode == CACHE_TYPE_TOOLTIP && isset($_GET['domain']))
Util::powerUseLocale($_GET['domain']);
if ($this->mode == CACHE_TYPE_TOOLTIP && $this->_get['domain'])
Util::powerUseLocale($this->_get['domain']);
$this->typeId = intVal($id);
$this->subject = new CurrencyList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound();
$this->notFound(Lang::game('currency'), Lang::currency('notFound'));
$this->name = $this->subject->getField('name', true);
}
@@ -46,7 +50,7 @@ class CurrencyPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$_itemId = $this->subject->getField('itemId');
@@ -56,9 +60,17 @@ class CurrencyPage extends GenericPage
$infobox = Lang::getInfoBoxForFlags(intval($this->subject->getField('cuFlags')));
// cap
if ($_ = $this->subject->getField('cap'))
$infobox[] = Lang::currency('cap').Lang::main('colon').Lang::nf($_);
// icon
if ($_ = $this->subject->getField('iconId'))
{
$infobox[] = Util::ucFirst(lang::game('icon')).Lang::main('colon').'[icondb='.$_.' name=true]';
$this->extendGlobalIds(Type::ICON, $_);
}
/****************/
/* Main Content */
/****************/
@@ -87,7 +99,7 @@ class CurrencyPage extends GenericPage
{
$this->extendGlobalData($lootTabs->jsGlobals);
foreach ($lootTabs->iterate() as list($file, $tabData))
foreach ($lootTabs->iterate() as [$file, $tabData])
$this->lvTabs[] = [$file, $tabData];
}
@@ -109,7 +121,8 @@ class CurrencyPage extends GenericPage
{
$items = [];
$tokens = [];
foreach ($vendors[$k] as $id => $qty)
// note: can only display one entry per row, so only use first entry of each vendor
foreach ($vendors[$k][0] as $id => $qty)
{
if (is_string($id))
continue;
@@ -120,16 +133,16 @@ class CurrencyPage extends GenericPage
$items[] = [-$id, $qty];
}
if ($vendors[$k]['event'])
if ($vendors[$k][0]['event'])
{
if (count($extraCols) == 3) // not already pushed
$extraCols[] = '$Listview.extraCols.condition';
$this->extendGlobalIds(TYPE_WORLDEVENT, $vendors[$k]['event']);
$row['condition'][0][$this->typeId][] = [[CND_ACTIVE_EVENT, $vendors[$k]['event']]];
$this->extendGlobalIds(Type::WORLDEVENT, $vendors[$k][0]['event']);
$row['condition'][0][$this->typeId][] = [[CND_ACTIVE_EVENT, $vendors[$k][0]['event']]];
}
$row['stock'] = $vendors[$k]['stock'];
$row['stock'] = $vendors[$k][0]['stock'];
$row['stack'] = $itemObj->getField('buyCount');
$row['cost'] = array(
$itemObj->getField('buyPrice'),
@@ -195,10 +208,10 @@ class CurrencyPage extends GenericPage
if (!$boughtBy->error)
{
$tabData = array(
'data' => array_values($boughtBy->getListviewData(ITEMINFO_VENDOR, [TYPE_CURRENCY => $this->typeId])),
'data' => array_values($boughtBy->getListviewData(ITEMINFO_VENDOR, [Type::CURRENCY => $this->typeId])),
'name' => '$LANG.tab_currencyfor',
'id' => 'currency-for',
'extraCols' => ["\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')"],
'extraCols' => ["\$Listview.funcBox.createSimpleCol('stack', 'stack', '10%', 'stack')", '$Listview.extraCols.cost'],
);
if ($boughtBy->getMatches() > CFG_SQL_LIMIT_DEFAULT)
@@ -211,43 +224,17 @@ class CurrencyPage extends GenericPage
}
}
protected function generateTooltip($asError = false)
protected function generateTooltip()
{
if ($asError)
return '$WowheadPower.registerCurrency('.$this->typeId.', '.User::$localeId.', {});';
$x = '$WowheadPower.registerCurrency('.$this->typeId.', '.User::$localeId.", {\n";
$x .= "\tname_".User::$localeString.": '".Util::jsEscape($this->subject->getField('name', true))."',\n";
$x .= "\ticon: '".rawurlencode($this->subject->getField('iconString', true, true))."',\n";
$x .= "\ttooltip_".User::$localeString.": '".$this->subject->renderTooltip()."'\n";
$x .= "});";
return $x;
$power = new StdClass();
if (!$this->subject->error)
{
$power->{'name_'.User::$localeString} = $this->subject->getField('name', true);
$power->icon = rawurlencode($this->subject->getField('iconString', true, true));
$power->{'tooltip_'.User::$localeString} = $this->subject->renderTooltip();
}
public function display($override = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::display($override);
if (!$this->loadCache($tt))
{
$tt = $this->generateTooltip();
$this->saveCache($tt);
}
header('Content-type: application/x-javascript; charset=utf-8');
die($tt);
}
public function notFound($title = '', $msg = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::notFound($title ?: Lang::game('currency'), $msg ?: Lang::currency('notFound'));
header('Content-type: application/x-javascript; charset=utf-8');
echo $this->generateTooltip(true);
exit();
return sprintf($this->powerTpl, $this->typeId, User::$localeId, Util::toJSON($power, JSON_AOWOW_POWER));
}
}

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabid 0: Database g_initHeader()
class EmotePage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_EMOTE;
protected $type = Type::EMOTE;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 100];
@@ -25,7 +25,7 @@ class EmotePage extends GenericPage
$this->subject = new EmoteList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound(Util::ucFirst(Lang::game('emote')), Lang::emote('notFound'));
$this->notFound(Lang::game('emote'), Lang::emote('notFound'));
$this->name = Util::ucFirst($this->subject->getField('cmd'));
}

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabid 0: Database g_initHeader()
class EmotesPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_EMOTE;
protected $type = Type::EMOTE;
protected $tpl = 'list-page-generic';
protected $path = [0, 100];
protected $tabId = 0;

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class EnchantmentPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_ENCHANTMENT;
protected $type = Type::ENCHANTMENT;
protected $typeId = 0;
protected $tpl = 'enchantment';
protected $path = [0, 101];
@@ -25,7 +25,7 @@ class EnchantmentPage extends GenericPage
$this->subject = new EnchantmentList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound(Util::ucFirst(Lang::game('enchantment')), Lang::enchantment('notFound'));
$this->notFound(Lang::game('enchantment'), Lang::enchantment('notFound'));
$this->extendGlobalData($this->subject->getJSGlobals());
@@ -64,7 +64,7 @@ class EnchantmentPage extends GenericPage
// reqskill
if ($_ = $this->subject->getField('skillLine'))
{
$this->extendGlobalIds(TYPE_SKILL, $_);
$this->extendGlobalIds(Type::SKILL, $_);
$foo = sprintf(Lang::game('requires'), '&nbsp;[skill='.$_.']');
if ($_ = $this->subject->getField('skillLevel'))
@@ -195,7 +195,7 @@ class EnchantmentPage extends GenericPage
{
$this->lvTabs[] = ['item', array(
'data' => array_values($socketsList->getListviewData()),
'name' => '$LANG.tab_usedby + \' \' + \''.Lang::item('socketBonus').'\'',
'name' => '$LANG.tab_socketbonus',
'id' => 'used-by-socketbonus',
)];

View File

@@ -8,14 +8,16 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class EnchantmentsPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_ENCHANTMENT;
protected $type = Type::ENCHANTMENT;
protected $tpl = 'enchantments';
protected $path = [0, 101];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $js = ['filters.js'];
protected $js = [[JS_FILE, 'filters.js']];
protected $_get = ['filter' => ['filter' => FILTER_UNSAFE_RAW]];
public function __construct($pageCall, $pageParam)
{
@@ -25,7 +27,7 @@ class EnchantmentsPage extends GenericPage
parent::__construct($pageCall, $pageParam);
$this->name = Util::ucFirst(Lang::game('enchantments'));
$this->subCat = $pageParam !== null ? '='.$pageParam : '';
$this->subCat = $pageParam !== '' ? '='.$pageParam : '';
}
protected function generateContent()
@@ -50,7 +52,7 @@ class EnchantmentsPage extends GenericPage
// recreate form selection
$this->filter = $this->filterObj->getForm();
$this->filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : NULL;
$this->filter['query'] = $this->_get['filter'];
$this->filter['initData'] = ['init' => 'enchantments'];
if ($x = $this->filterObj->getSetCriteria())
@@ -100,8 +102,8 @@ class EnchantmentsPage extends GenericPage
protected function generatePath()
{
$form = $this->filterObj->getForm('form');
if (isset($form['ty']) && !is_array($form['ty']))
$this->path[] = $form['ty'];
if (isset($form['ty']) && count($form['ty']) == 1)
$this->path[] = $form['ty'][0];
}
}

View File

@@ -8,15 +8,18 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class EventPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_WORLDEVENT;
protected $type = Type::WORLDEVENT;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 11];
protected $tabId = 0;
protected $mode = CACHE_TYPE_PAGE;
protected $_get = ['domain' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkDomain']];
private $powerTpl = '$WowheadPower.registerHoliday(%d, %d, %s);';
private $hId = 0;
private $eId = 0;
@@ -25,14 +28,14 @@ class EventPage extends GenericPage
parent::__construct($pageCall, $id);
// temp locale
if ($this->mode == CACHE_TYPE_TOOLTIP && isset($_GET['domain']))
Util::powerUseLocale($_GET['domain']);
if ($this->mode == CACHE_TYPE_TOOLTIP && $this->_get['domain'])
Util::powerUseLocale($this->_get['domain']);
$this->typeId = intVal($id);
$this->subject = new WorldEventList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound();
$this->notFound(Lang::game('event'), Lang::event('notFound'));
$this->hId = $this->subject->getField('holidayId');
$this->eId = $this->typeId;
@@ -64,7 +67,7 @@ class EventPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
/***********/
/* Infobox */
@@ -75,7 +78,7 @@ class EventPage extends GenericPage
// boss
if ($_ = $this->subject->getField('bossCreature'))
{
$this->extendGlobalIds(TYPE_NPC, $_);
$this->extendGlobalIds(Type::NPC, $_);
$this->infobox[] = Lang::npc('rank', 3).Lang::main('colon').'[npc='.$_.']';
}
@@ -184,10 +187,10 @@ class EventPage extends GenericPage
$this->lvTabs[] = ['quest', $tabData];
$questItems = [];
foreach (array_column($quests->rewards, TYPE_ITEM) as $arr)
foreach (array_column($quests->rewards, Type::ITEM) as $arr)
$questItems = array_merge($questItems, $arr);
foreach (array_column($quests->requires, TYPE_ITEM) as $arr)
foreach (array_column($quests->requires, Type::ITEM) as $arr)
$questItems = array_merge($questItems, $arr);
if ($questItems)
@@ -247,7 +250,7 @@ class EventPage extends GenericPage
if ($r <= 0)
continue;
$this->extendGlobalIds(TYPE_WORLDEVENT, $r);
$this->extendGlobalIds(Type::WORLDEVENT, $r);
$d = $this->subject->getListviewData();
$d[$this->eId]['condition'][0][$this->typeId][] = [[-CND_ACTIVE_EVENT, $r]];
@@ -266,6 +269,22 @@ class EventPage extends GenericPage
}
}
protected function generateTooltip() : string
{
$power = new StdClass();
if (!$this->subject->error)
{
$power->{'name_'.User::$localeString} = $this->subject->getField('name', true);
if ($this->subject->getField('iconString') != 'trade_engineering')
$power->icon = rawurlencode($this->subject->getField('iconString', true, true));
$power->{'tooltip_'.User::$localeString} = $this->subject->renderTooltip();
}
return sprintf($this->powerTpl, $this->typeId, User::$localeId, Util::toJSON($power, JSON_AOWOW_POWER));
}
protected function postCache()
{
// update dates to now()
@@ -326,50 +345,6 @@ class EventPage extends GenericPage
}
}
}
protected function generateTooltip($asError = false)
{
if ($asError)
return '$WowheadPower.registerHoliday('.$this->typeId.', '.User::$localeId.', {});';
$x = '$WowheadPower.registerHoliday('.$this->typeId.', '.User::$localeId.", {\n";
$x .= "\tname_".User::$localeString.": '".Util::jsEscape($this->subject->getField('name', true))."',\n";
if ($this->subject->getField('iconString') != 'trade_engineering')
$x .= "\ticon: '".rawurlencode($this->subject->getField('iconString', true, true))."',\n";
$x .= "\ttooltip_".User::$localeString.": '".$this->subject->renderTooltip()."'\n";
$x .= "});";
return $x;
}
public function display($override = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::display($override);
if (!$this->loadCache($tt))
{
$tt = $this->generateTooltip();
$this->saveCache($tt);
}
list($start, $end) = $this->postCache();
header('Content-type: application/x-javascript; charset=utf-8');
die(sprintf($tt, $start, $end));
}
public function notFound($title = '', $msg = '')
{
if ($this->mode != CACHE_TYPE_TOOLTIP)
return parent::notFound($title ?: Lang::game('event'), $msg ?: Lang::event('notFound'));
header('Content-type: application/x-javascript; charset=utf-8');
echo $this->generateTooltip(true);
exit();
}
}
?>

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class EventsPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_WORLDEVENT;
protected $type = Type::WORLDEVENT;
protected $tpl = 'list-page-generic';
protected $path = [0, 11];
protected $tabId = 0;

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class FactionPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_FACTION;
protected $type = Type::FACTION;
protected $typeId = 0;
protected $tpl = 'detail-page-generic';
protected $path = [0, 7];
@@ -48,7 +48,7 @@ class FactionPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=zones&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
/***********/
/* Infobox */
@@ -58,7 +58,7 @@ class FactionPage extends GenericPage
// Quartermaster if any
if ($ids = $this->subject->getField('qmNpcIds'))
{
$this->extendGlobalIds(TYPE_NPC, $ids);
$this->extendGlobalIds(Type::NPC, ...$ids);
$qmStr = Lang::faction('quartermaster').Lang::main('colon');
@@ -142,7 +142,7 @@ class FactionPage extends GenericPage
case 'creature_rate': $buff .= '[tr][td]'.Lang::game('npcs') .Lang::main('colon').'[/td]'; break;
case 'spell_rate': $buff .= '[tr][td]'.Lang::game('spells') .Lang::main('colon').'[/td]'; break;
default:
continue;
continue 2;
}
$buff .= '[td width=35px align=right][span class=q'.($v < 1 ? '10]' : '2]+').intVal(($v - 1) * 100).'%[/span][/td][/tr]';

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class FactionsPage extends GenericPage
{
use ListPage;
use TrListPage;
protected $type = TYPE_FACTION;
protected $type = Type::FACTION;
protected $tpl = 'list-page-generic';
protected $path = [0, 7];
protected $tabId = 0;

View File

@@ -4,9 +4,8 @@ if (!defined('AOWOW_REVISION'))
die('illegal access');
trait DetailPage
trait TrDetailPage
{
protected $hasComContent = true;
protected $category = null; // not used on detail pages
protected $lvTabs = []; // most pages have this
@@ -18,7 +17,7 @@ trait DetailPage
protected $contribute = CONTRIBUTE_ANY;
protected function generateCacheKey($withStaff = true)
protected function generateCacheKey(bool $withStaff = true) : string
{
$staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE));
@@ -32,7 +31,7 @@ trait DetailPage
return implode('_', $key);
}
protected function applyCCErrors()
protected function applyCCErrors() : void
{
if (!empty($_SESSION['error']['co']))
$this->coError = $_SESSION['error']['co'];
@@ -48,7 +47,7 @@ trait DetailPage
}
trait ListPage
trait TrListPage
{
protected $category = null;
protected $filter = [];
@@ -56,7 +55,7 @@ trait ListPage
private $filterObj = null;
protected function generateCacheKey($withStaff = true)
protected function generateCacheKey(bool $withStaff = true) : string
{
$staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE));
@@ -73,6 +72,7 @@ trait ListPage
}
}
trait TrProfiler
{
protected $region = '';
@@ -85,22 +85,29 @@ trait TrProfiler
protected $doResync = null;
protected function getSubjectFromUrl($str)
protected function generateCacheKey(bool $withStaff = true) : string
{
if (!$str)
$staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE));
// mode, type, typeId, employee-flag, localeId, category, filter
$key = [$this->mode, $this->type, $this->subject->getField('id'), $staff, User::$localeId, '-1', '-1'];
return implode('_', $key);
}
protected function getSubjectFromUrl(string $pageParam) : void
{
if (!$pageParam)
return;
// cat[0] is always region
// cat[1] is realm or bGroup (must be realm if cat[2] is set)
// cat[2] is arena-team, guild or player
$cat = explode('.', $str, 3);
$cat = explode('.', $pageParam, 3);
$cat = array_map('urldecode', $cat);
if (count($cat) > 3)
return;
if ($cat[0] !== 'eu' && $cat[0] !== 'us')
if (array_search($cat[0], Util::$regions) === false)
return;
$this->region = $cat[0];
@@ -111,11 +118,11 @@ trait TrProfiler
{
foreach (Profiler::getRealms() as $rId => $r)
{
if (Profiler::urlize($r['name']) == $cat[1])
if (Profiler::urlize($r['name'], true) == $cat[1])
{
$this->realm = $r['name'];
$this->realmId = $rId;
if (isset($cat[2]) && mb_strlen($cat[2]) >= 3)
if (isset($cat[2]) && mb_strlen($cat[2]) >= 2)
$this->subjectName = $cat[2]; // cannot reconstruct original name from urlized form; match against special name field
break;
@@ -124,44 +131,49 @@ trait TrProfiler
}
}
protected function initialSync()
protected function initialSync() : void
{
$this->prepareContent();
$this->contribute = CONTRIBUTE_NONE;
$this->notFound = array(
'title' => sprintf(Lang::profiler('firstUseTitle'), $this->subjectName, $this->realm),
'msg' => ''
);
$this->hasComContent = false;
Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
if (isset($this->tabId))
$this->pageTemplate['activeTab'] = $this->tabId;
$this->sumSQLStats();
$this->display('text-page-generic');
exit();
}
protected function generatePath()
protected function generatePath() : void
{
if ($this->region)
{
$this->path[] = $this->region;
if ($this->realm)
$this->path[] = Profiler::urlize($this->realm);
$this->path[] = Profiler::urlize($this->realm, true);
// else
// $this->path[] = Profiler::urlize(CFG_BATTLEGROUP);
}
}
}
class GenericPage
{
use TrRequestData;
protected $tpl = '';
protected $reqUGroup = U_GROUP_NONE;
protected $reqAuth = false;
protected $mode = CACHE_TYPE_NONE;
// protected $contribute; // defined in __construct()
protected $jsGlobals = [];
protected $lvData = [];
@@ -191,6 +203,7 @@ class GenericPage
private $lvTemplates = array(
'achievement' => ['template' => 'achievement', 'id' => 'achievements', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_achievements' ],
'areatrigger' => ['template' => 'areatrigger', 'id' => 'areatrigger', 'parent' => 'lv-generic', 'data' => [], ],
'calendar' => ['template' => 'holidaycal', 'id' => 'calendar', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_calendar' ],
'class' => ['template' => 'classs', 'id' => 'classes', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_classes' ],
'commentpreview' => ['template' => 'commentpreview', 'id' => 'comments', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_comments' ],
@@ -204,6 +217,7 @@ class GenericPage
'icongallery' => ['template' => 'icongallery', 'id' => 'icons', 'parent' => 'lv-generic', 'data' => [] ],
'item' => ['template' => 'item', 'id' => 'items', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_items' ],
'itemset' => ['template' => 'itemset', 'id' => 'itemsets', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_itemsets' ],
'mail' => ['template' => 'mail', 'id' => 'mails', 'parent' => 'lv-generic', 'data' => [] ],
'model' => ['template' => 'model', 'id' => 'gallery', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_gallery' ],
'object' => ['template' => 'object', 'id' => 'objects', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_objects' ],
'pet' => ['template' => 'pet', 'id' => 'hunter-pets', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_pets' ],
@@ -219,13 +233,19 @@ class GenericPage
'title' => ['template' => 'title', 'id' => 'titles', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_titles' ],
'topusers' => ['template' => 'topusers', 'id' => 'topusers', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.topusers' ],
'video' => ['template' => 'video', 'id' => 'videos', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_videos' ],
'zone' => ['template' => 'zone', 'id' => 'zones', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_zones' ]
'zone' => ['template' => 'zone', 'id' => 'zones', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_zones' ],
'guide' => ['template' => 'guide', 'id' => 'guides', 'parent' => 'lv-generic', 'data' => [], ]
);
public function __construct($pageCall, $pageParam = null)
public function __construct(string $pageCall = '', string $pageParam = '')
{
$this->time = microtime(true);
$this->initRequestData();
if (!isset($this->contribute))
$this->contribute = CONTRIBUTE_NONE;
$this->fullParams = $pageCall;
if ($pageParam)
$this->fullParams .= '='.$pageParam;
@@ -256,6 +276,7 @@ class GenericPage
$this->headerLogo = Util::defStatic($ahl);
$this->gUser = User::getUserGlobals();
$this->gFavorites = User::getFavorites();
$this->pageTemplate['pageName'] = strtolower($pageCall);
if (!$this->isValidPage())
@@ -264,7 +285,7 @@ class GenericPage
// requires authed user
if ($this->reqAuth && !User::$id)
$this->forwardToSignIn($_SERVER['QUERY_STRING']);
$this->forwardToSignIn($_SERVER['QUERY_STRING'] ?? '');
// restricted access
if ($this->reqUGroup && !User::isInGroup($this->reqUGroup))
@@ -272,7 +293,7 @@ class GenericPage
if (User::$id)
$this->error();
else
$this->forwardToSignIn($_SERVER['QUERY_STRING']);
$this->forwardToSignIn($_SERVER['QUERY_STRING'] ?? '');
}
if (CFG_MAINTENANCE && !User::isInGroup(U_GROUP_EMPLOYEE))
@@ -285,11 +306,13 @@ class GenericPage
$this->applyCCErrors();
}
/**********/
/* Checks */
/**********/
private function isSaneInclude($path, $file) // "template_exists"
// "template_exists"
private function isSaneInclude(string $path, string $file) : bool
{
if (preg_match('/[^\w\-]/i', str_replace('admin/', '', $file)))
return false;
@@ -300,7 +323,8 @@ class GenericPage
return true;
}
private function isValidPage() // has a valid combination of categories
// has a valid combination of categories
private function isValidPage() : bool
{
if (!isset($this->category) || empty($this->validCats))
return true;
@@ -330,26 +354,34 @@ class GenericPage
return false;
}
/****************/
/* Prepare Page */
/****************/
protected function prepareContent() // get from cache ?: run generators
// get from cache ?: run generators
protected function prepareContent() : void
{
if (!$this->loadCache())
{
$this->addArticle();
$this->generateContent();
$this->generatePath();
$this->generateTitle();
$this->addArticle();
$this->applyGlobals();
$this->saveCache();
}
if (isset($this->type) && isset($this->typeId))
if ($this instanceof GuidePage)
{
$this->gPageInfo = ['name' => $this->name];
if (isset($this->author))
$this->gPageInfo['author'] = $this->author;
}
else if (isset($this->type) && isset($this->typeId))
{
$this->gPageInfo = array( // varies slightly for special pages like maps, user-dashboard or profiler
'type' => $this->type,
@@ -357,7 +389,9 @@ class GenericPage
'name' => $this->name
);
}
else if (!empty($this->articleUrl))
// only adds edit links to the staff menu: precursor to guides?
if (!empty($this->articleUrl) && !($this instanceof GuidePage && $this->show == GuidePage::SHOW_GUIDE))
{
$this->gPageInfo = array(
'articleUrl' => $this->fullParams, // is actually be the url-param
@@ -375,70 +409,81 @@ class GenericPage
$this->postCache();
// determine contribute tabs
if (isset($this->subject))
if (isset($this->subject) && !isset($this->contribute))
{
$x = get_class($this->subject);
$this->contribute = $x::$contribute;
}
if (!empty($this->hasComContent)) // get comments, screenshots, videos
if ($this->contribute & CONTRIBUTE_CO)
$this->community['co'] = CommunityContent::getComments($this->type, $this->typeId);
if ($this->contribute & CONTRIBUTE_SS)
$this->community['ss'] = CommunityContent::getScreenshots($this->type, $this->typeId);
if ($this->contribute & CONTRIBUTE_VI)
$this->community['vi'] = CommunityContent::getVideos($this->type, $this->typeId);
// as comments are not cached, those globals cant be either
if ($this->contribute)
{
$this->community = CommunityContent::getAll($this->type, $this->typeId, $jsGlobals);
$this->extendGlobalData($jsGlobals); // as comments are not cached, those globals cant be either
$this->extendGlobalData(CommunityContent::getJSGlobals());
$this->applyGlobals();
}
$this->time = microtime(true) - $this->time;
Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
$this->sumSQLStats();
}
public function addJS($name, $unshift = false)
public function addScript(array ...$structs) : void
{
if (is_array($name))
foreach ($structs as $s) // iType, sContent, bFront, sIeCnd
{
foreach ($name as $n)
$this->addJS($n, $unshift);
if (empty($s[1]))
{
trigger_error('GenericPage::addScript - content empty', E_USER_WARNING);
continue;
}
else if (!in_array($name, $this->js))
$s = array_pad($s, 4, '');
switch ($s[0])
{
if ($unshift)
array_unshift($this->js, $name);
case JS_FILE:
case JS_STRING:
if (empty($s[2]))
$this->js[] = $s;
else
$this->js[] = $name;
}
}
public function addCSS($struct, $unshift = false)
{
if (is_array($struct) && empty($struct['path']) && empty($struct['string']))
{
foreach ($struct as $s)
$this->addCSS($s, $unshift);
}
else if (!in_array($struct, $this->css))
{
if ($unshift)
array_unshift($this->css, $struct);
array_unshift($this->js, $s);
break;
case CSS_FILE:
case CSS_STRING:
if (empty($s[2]))
$this->css[] = $s;
else
$this->css[] = $struct;
array_unshift($this->css, $s);
break;
default:
trigger_error('GenericPage::addScript - unknown script type #'.$s[0], E_USER_WARNING);
}
}
}
private function addArticle() // get article & static infobox (run before processing jsGlobals)
// get article & static infobox (run before processing jsGlobals)
private function addArticle() :void
{
if (isset($this->article))
return;
$article = [];
if (!empty($this->type) && isset($this->typeId))
{
$article = DB::Aowow()->selectRow('SELECT article, quickInfo, locale, editAccess FROM ?_articles WHERE type = ?d AND typeId = ?d AND locale = ?d UNION ALL SELECT article, quickInfo, locale, editAccess FROM ?_articles WHERE type = ?d AND typeId = ?d AND locale = 0 ORDER BY locale DESC LIMIT 1',
$this->type, $this->typeId, User::$localeId, $this->type, $this->typeId
);
}
if (isset($this->guideRevision))
$article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d AND `rev` = ?d',
Type::GUIDE, $this->typeId, $this->guideRevision);
else if (!empty($this->articleUrl))
{
$article = DB::Aowow()->selectRow('SELECT article, quickInfo, locale, editAccess FROM ?_articles WHERE url = ? AND locale = ?d UNION ALL SELECT article, quickInfo, locale, editAccess FROM ?_articles WHERE url = ? AND locale = 0 ORDER BY locale DESC LIMIT 1',
$this->articleUrl, User::$localeId, $this->articleUrl
);
}
$article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `url` = ? AND `locale` IN (?a) ORDER BY `locale`, `rev` DESC LIMIT 1',
$this->articleUrl, [User::$localeId, LOCALE_EN]);
else if (!empty($this->type) && isset($this->typeId))
$article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d AND `locale` IN (?a) ORDER BY `locale`, `rev` DESC LIMIT 1',
$this->type, $this->typeId, [User::$localeId, LOCALE_EN]);
if ($article)
{
@@ -480,7 +525,8 @@ class GenericPage
}
}
private function addAnnouncements() // get announcements and notes for user
// get announcements and notes for user
private function addAnnouncements() : void
{
if (!isset($this->announcements))
$this->announcements = [];
@@ -527,9 +573,9 @@ class GenericPage
}
}
protected function getCategoryFromUrl($str)
protected function getCategoryFromUrl(string $urlParam) : void
{
$arr = explode('.', $str);
$arr = explode('.', $urlParam);
$params = [];
foreach ($arr as $v)
@@ -539,37 +585,61 @@ class GenericPage
$this->category = $params;
}
protected function forwardToSignIn($next = '')
protected function forwardToSignIn(string $next = '') : void
{
$next = $next ? '&next='.$next : '';
header('Location: ?account=signin'.$next, true, 302);
}
protected function sumSQLStats() : void
{
Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
}
/*******************/
/* Special Display */
/*******************/
public function notFound($title, $msg = '') // unknown entry
// unknown entry
public function notFound(string $title = '', string $msg = '') : void
{
if ($this->mode == CACHE_TYPE_TOOLTIP && method_exists($this, 'generateTooltip'))
{
header(MIME_TYPE_JSON);
echo $this->generateTooltip();
}
else if ($this->mode == CACHE_TYPE_XML && method_exists($this, 'generateXML'))
{
header(MIME_TYPE_XML);
echo $this->generateXML();
}
else
{
header('HTTP/1.0 404 Not Found', true, 404);
array_unshift($this->title, Lang::main('nfPageTitle'));
$this->contribute = CONTRIBUTE_NONE;
$this->notFound = array(
'title' => isset($this->typeId) ? Util::ucFirst($title).' #'.$this->typeId : $title,
'msg' => !$msg && isset($this->typeId) ? sprintf(Lang::main('pageNotFound'), $title) : $msg
);
$this->hasComContent = false;
Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
if (isset($this->tabId))
$this->pageTemplate['activeTab'] = $this->tabId;
header('HTTP/1.0 404 Not Found', true, 404);
$this->sumSQLStats();
$this->display('list-page-generic');
}
exit();
}
public function error() // unknown page
// unknown page
public function error() : void
{
$this->path = null;
$this->tabId = null;
@@ -580,7 +650,7 @@ class GenericPage
$this->addArticle();
Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
$this->sumSQLStats();
header('HTTP/1.0 404 Not Found', true, 404);
@@ -588,7 +658,8 @@ class GenericPage
exit();
}
public function maintenance() // display brb gnomes
// display brb gnomes
public function maintenance() : void
{
header('HTTP/1.0 503 Service Temporarily Unavailable', true, 503);
header('Retry-After: '.(3 * HOUR));
@@ -597,16 +668,24 @@ class GenericPage
exit();
}
/*******************/
/* General Display */
/*******************/
public function display($override = '') // load given template string or GenericPage::$tpl
// load given template string or GenericPage::$tpl
public function display(string $override = '') : void
{
// Heisenbug: IE11 and FF32 will sometimes (under unknown circumstances) cache 302 redirects and stop
// re-requesting them from the server but load them from local cache, thus breaking menu features.
Util::sendNoCacheHeader();
if ($this->mode == CACHE_TYPE_TOOLTIP && method_exists($this, 'generateTooltip'))
$this->displayExtra([$this, 'generateTooltip']);
else if ($this->mode == CACHE_TYPE_XML && method_exists($this, 'generateXML'))
$this->displayExtra([$this, 'generateXML'], MIME_TYPE_XML);
else
{
if (isset($this->tabId))
$this->pageTemplate['activeTab'] = $this->tabId;
@@ -635,11 +714,34 @@ class GenericPage
else
$this->error();
}
}
public function writeGlobalVars() // load jsGlobal
// generate and cache
public function displayExtra(callable $generator, string $mime = MIME_TYPE_JSON) : void
{
$outString = '';
if (!$this->loadCache($outString))
{
$outString = $generator();
$this->saveCache($outString);
}
header($mime);
if (method_exists($this, 'postCache') && ($pc = $this->postCache()))
die(sprintf($outString, ...$pc));
else
die($outString);
}
// load jsGlobal
public function writeGlobalVars() : string
{
$buff = '';
if (!empty($this->guideRating))
$buff .= sprintf(Util::$guideratingString, ...$this->guideRating);
foreach ($this->jsGlobals as $type => $struct)
{
$buff .= " var _ = ".$struct[0].';';
@@ -648,8 +750,8 @@ class GenericPage
{
foreach ($data as $k => $v)
{
// localizes expected fields
if (in_array($k, ['name', 'namefemale']))
// localizes expected fields .. except for icons .. icons are special
if (in_array($k, ['name', 'namefemale']) && $struct[0] != Type::getJSGlobalString(Type::ICON))
{
$data[$k.'_'.User::$localeString] = $v;
unset($data[$k]);
@@ -682,7 +784,8 @@ class GenericPage
return $buff;
}
public function brick($file, array $localVars = []) // load brick
// load brick
public function brick(string $file, array $localVars = []) : void
{
foreach ($localVars as $n => $v)
$$n = $v;
@@ -693,7 +796,8 @@ class GenericPage
include('template/bricks/'.$file.'.tpl.php');
}
public function lvBrick($file) // load listview addIns
// load listview addIns
public function lvBrick(string $file) : void
{
if (!$this->isSaneInclude('template/listviews/', $file))
trigger_error('Nonexistant Listview addin requested: template/listviews/'.$file.'.tpl.php', E_USER_ERROR);
@@ -701,7 +805,8 @@ class GenericPage
include('template/listviews/'.$file.'.tpl.php');
}
public function localizedBrick($file, $loc = LOCALE_EN) // load brick with more text then vars
// load brick with more text then vars
public function localizedBrick(string $file, int $loc = LOCALE_EN) : void
{
if (!$this->isSaneInclude('template/localized/', $file.'_'.$loc))
{
@@ -714,28 +819,26 @@ class GenericPage
include('template/localized/'.$file.'_'.$loc.'.tpl.php');
}
/**********************/
/* Prepare js-Globals */
/**********************/
public function extendGlobalIds($type, $data) // add typeIds <int|array[int]> that should be displayed as jsGlobal on the page
// add typeIds <int|array[int]> that should be displayed as jsGlobal on the page
public function extendGlobalIds(int $type, int ...$ids) : void
{
if (!$type || !$data)
return false;
if (!$type || !$ids)
return;
if (!isset($this->jsgBuffer[$type]))
$this->jsgBuffer[$type] = [];
if (is_array($data))
{
foreach ($data as $id)
$this->jsgBuffer[$type][] = (int)$id;
}
else if (is_numeric($data))
$this->jsgBuffer[$type][] = (int)$data;
foreach ($ids as $id)
$this->jsgBuffer[$type][] = $id;
}
public function extendGlobalData($data, $extra = null) // add jsGlobals or typeIds (can be mixed in one array: TYPE => [mixeddata]) to display on the page
// add jsGlobals or typeIds (can be mixed in one array: TYPE => [mixeddata]) to display on the page
public function extendGlobalData(array $data, ?array $extra = null) : void
{
foreach ($data as $type => $globals)
{
@@ -760,41 +863,20 @@ class GenericPage
$this->jsGlobals[$type][2] = $extra;
}
private function initJSGlobal($type) // init store for type
// init store for type
private function initJSGlobal(int $type) : void
{
$jsg = &$this->jsGlobals; // shortcut
if (isset($jsg[$type]))
return;
switch ($type)
{ // [varName, [data], [extra]]
case TYPE_NPC: $jsg[TYPE_NPC] = ['g_npcs', [], []]; break;
case TYPE_OBJECT: $jsg[TYPE_OBJECT] = ['g_objects', [], []]; break;
case TYPE_ITEM: $jsg[TYPE_ITEM] = ['g_items', [], []]; break;
case TYPE_ITEMSET: $jsg[TYPE_ITEMSET] = ['g_itemsets', [], []]; break;
case TYPE_QUEST: $jsg[TYPE_QUEST] = ['g_quests', [], []]; break;
case TYPE_SPELL: $jsg[TYPE_SPELL] = ['g_spells', [], []]; break;
case TYPE_ZONE: $jsg[TYPE_ZONE] = ['g_gatheredzones', [], []]; break;
case TYPE_FACTION: $jsg[TYPE_FACTION] = ['g_factions', [], []]; break;
case TYPE_PET: $jsg[TYPE_PET] = ['g_pets', [], []]; break;
case TYPE_ACHIEVEMENT: $jsg[TYPE_ACHIEVEMENT] = ['g_achievements', [], []]; break;
case TYPE_TITLE: $jsg[TYPE_TITLE] = ['g_titles', [], []]; break;
case TYPE_WORLDEVENT: $jsg[TYPE_WORLDEVENT] = ['g_holidays', [], []]; break;
case TYPE_CLASS: $jsg[TYPE_CLASS] = ['g_classes', [], []]; break;
case TYPE_RACE: $jsg[TYPE_RACE] = ['g_races', [], []]; break;
case TYPE_SKILL: $jsg[TYPE_SKILL] = ['g_skills', [], []]; break;
case TYPE_CURRENCY: $jsg[TYPE_CURRENCY] = ['g_gatheredcurrencies', [], []]; break;
case TYPE_SOUND: $jsg[TYPE_SOUND] = ['g_sounds', [], []]; break;
case TYPE_ICON: $jsg[TYPE_ICON] = ['g_icons', [], []]; break;
// well, this is awkward
case TYPE_USER: $jsg[TYPE_USER] = ['g_users', [], []]; break;
case TYPE_EMOTE: $jsg[TYPE_EMOTE] = ['g_emotes', [], []]; break;
case TYPE_ENCHANTMENT: $jsg[TYPE_ENCHANTMENT] = ['g_enchantments', [], []]; break;
}
if ($tpl = Type::getJSGlobalTemplate($type))
$jsg[$type] = $tpl;
}
private function applyGlobals() // lookup jsGlobals from collected typeIds
// lookup jsGlobals from collected typeIds
private function applyGlobals() : void
{
foreach ($this->jsgBuffer as $type => $ids)
{
@@ -807,33 +889,9 @@ class GenericPage
$this->initJSGlobal($type);
$cnd = [CFG_SQL_LIMIT_NONE, ['id', array_unique($ids, SORT_NUMERIC)]];
switch ($type)
{
case TYPE_NPC: $obj = new CreatureList($cnd); break;
case TYPE_OBJECT: $obj = new GameobjectList($cnd); break;
case TYPE_ITEM: $obj = new ItemList($cnd); break;
case TYPE_ITEMSET: $obj = new ItemsetList($cnd); break;
case TYPE_QUEST: $obj = new QuestList($cnd); break;
case TYPE_SPELL: $obj = new SpellList($cnd); break;
case TYPE_ZONE: $obj = new ZoneList($cnd); break;
case TYPE_FACTION: $obj = new FactionList($cnd); break;
case TYPE_PET: $obj = new PetList($cnd); break;
case TYPE_ACHIEVEMENT: $obj = new AchievementList($cnd); break;
case TYPE_TITLE: $obj = new TitleList($cnd); break;
case TYPE_WORLDEVENT: $obj = new WorldEventList($cnd); break;
case TYPE_CLASS: $obj = new CharClassList($cnd); break;
case TYPE_RACE: $obj = new CharRaceList($cnd); break;
case TYPE_SKILL: $obj = new SkillList($cnd); break;
case TYPE_CURRENCY: $obj = new CurrencyList($cnd); break;
case TYPE_SOUND: $obj = new SoundList($cnd); break;
// "um, eh":, he ums and ehs.
case TYPE_USER: $obj = new UserList($cnd); break;
case TYPE_EMOTE: $obj = new EmoteList($cnd); break;
case TYPE_ENCHANTMENT: $obj = new EnchantmentList($cnd); break;
default: continue;
}
$obj = Type::newList($type, [CFG_SQL_LIMIT_NONE, ['id', array_unique($ids, SORT_NUMERIC)]]);
if (!$obj)
continue;
$this->extendGlobalData($obj->getJSGlobals(GLOBALINFO_SELF));
@@ -842,14 +900,16 @@ class GenericPage
}
}
/*********/
/* Cache */
/*********/
public function saveCache($saveString = null) // visible properties or given strings are cached
// visible properties or given strings are cached
private function saveCache(string $saveString = '') : void
{
if ($this->mode == CACHE_TYPE_NONE)
return false;
return;
if (!CFG_CACHE_MODE || CFG_DEBUG)
return;
@@ -863,8 +923,8 @@ class GenericPage
{
try
{
// public, protected and an undocumented flag added to properties created on the fly..?
if ((new ReflectionProperty($this, $key))->getModifiers() & 0x1300)
$rp = new ReflectionProperty($this, $key);
if ($rp && ($rp->isPublic() || $rp->isProtected()))
if (!in_array($key, $noCache))
$cache[$key] = $val;
}
@@ -872,7 +932,7 @@ class GenericPage
}
}
else
$cache = (string)$saveString;
$cache = $saveString;
if (CFG_CACHE_MODE & CACHE_MODE_MEMCACHED)
{
@@ -927,7 +987,7 @@ class GenericPage
}
}
public function loadCache(&$saveString = null)
private function loadCache(string &$saveString = '') : bool
{
if ($this->mode == CACHE_TYPE_NONE)
return false;
@@ -966,7 +1026,7 @@ class GenericPage
if (substr_count($cache[0], ' ') < 2)
return false;
list($time, $rev, $type) = explode(' ', $cache[0]);
[$time, $rev, $type] = explode(' ', $cache[0]);
if ($time + CFG_CACHE_DECAY <= time() || $rev != AOWOW_REVISION)
$cache = null;
@@ -999,7 +1059,7 @@ class GenericPage
return false;;
}
private function memcached()
private function memcached() : Memcached
{
if (!$this->memcached && (CFG_CACHE_MODE & CACHE_MODE_MEMCACHED))
{

555
pages/guide.php Normal file
View File

@@ -0,0 +1,555 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// menuId ?: Category g_initPath()
// tabid 6: Guides g_initHeader()
class GuidePage extends GenericPage
{
use TrDetailPage;
const SHOW_NEW = 1;
const SHOW_EDITOR = 2;
const SHOW_GUIDE = 3;
const SHOW_CHANGELOG = 4;
const VALID_URL = '/^[a-z0-9=_&\.\/\-]{2,64}$/i';
protected /* int */ $type = Type::GUIDE;
protected /* int */ $typeId = 0;
protected /* int */ $guideRevision = -1;
protected /* string */ $tpl = 'detail-page-generic';
protected /* array */ $path = [6];
protected /* int */ $tabId = 6;
protected /* int */ $mode = CACHE_TYPE_PAGE;
protected /* string */ $author = '';
protected /* array */ $gPageInfo = [];
protected /* int */ $show = self::SHOW_GUIDE;
protected /* int */ $articleUrl = '';
private /* array */ $validCats = [1, 2, 3, 4, 5, 6, 7, 8, 9];
private /* string */ $extra = '';
private /* string */ $powerTpl = '$WowheadPower.registerGuide(%s, %d, %s);';
private /* array */ $editorFields = [];
protected /* array */ $_get = array(
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'rev' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt']
);
protected /* array */ $_post = array(
'save' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkEmptySet'],
'submit' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkEmptySet'],
'title' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'name' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'description' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'changelog' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'body' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkFulltext'],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'category' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'specId' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt'],
'classId' => ['filter' => FILTER_CALLBACK, 'options' => 'GenericPage::checkInt']
);
public function __construct($pageCall, $pageParam)
{
$this->contribute = CONTRIBUTE_CO;
$guide = explode( "&", $pageParam, 2);
parent::__construct($pageCall, $pageParam);
if (isset($guide[1]) && preg_match(self::VALID_URL, $guide[1]))
$this->extra = $guide[1];
/**********************/
/* get mode + guideId */
/**********************/
if (Util::checkNumeric($guide[0], NUM_CAST_INT))
$this->typeId = $guide[0];
else if (preg_match(self::VALID_URL, $guide[0]))
{
switch ($guide[0])
{
case 'changelog':
if (!$this->_get['id'])
break;
$this->show = self::SHOW_CHANGELOG;
$this->tpl = 'text-page-generic';
$this->article = false; // do not include article from db
// main container should be tagged: <div class="text guide-changelog">
// why is this here: is there a mediawiki like diff function for staff?
$this->addScript([CSS_STRING, 'li input[type="radio"] {margin:0}']);
$this->typeId = $this->_get['id']; // just to display sensible not-found msg
if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `id` = ?d', $this->typeId))
$this->typeId = intVal($id);
break;
case 'new':
if (User::canWriteGuide())
{
$this->show = self::SHOW_NEW;
$this->guideRevision = null;
$this->initNew();
return; // do not create new GuideList
}
break;
case 'edit':
if (User::canWriteGuide())
{
if (!$this->initEdit())
$this->notFound(Lang::guide('guide'), Lang::guide('notFound'));
$this->show = self::SHOW_EDITOR;
}
break;
default:
if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `url` = ?', Util::lower($guide[0])))
{
$this->typeId = intVal($id);
$this->guideRevision = null;
$this->articleUrl = Util::lower($guide[0]);
}
}
}
/*********************/
/* load actual guide */
/*********************/
$this->subject = new GuideList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound(Lang::guide('guide'), Lang::guide('notFound'));
if (!$this->subject->canBeViewed() && !$this->subject->userCanView())
header('Location: ?guides='.$this->subject->getField('category'), true, 302);
if ($this->show == self::SHOW_GUIDE && $this->_get['rev'] !== null && !$this->articleUrl && $this->subject->userCanView())
$this->guideRevision = $this->_get['rev'];
else if ($this->show == self::SHOW_GUIDE && !$this->articleUrl)
$this->guideRevision = $this->subject->getField('rev');
else
$this->guideRevision = null;
if (!$this->name)
$this->name = $this->subject->getField('name');
}
protected function generateContent() : void
{
/*
match ($this->show)
{
self::SHOW_NEW => $this->displayNew(),
self::SHOW_EDITOR => $this->displayEditor(),
self::SHOW_GUIDE => $this->displayGuide(),
self::SHOW_CHANGELOG => $this->displayChangelog(),
default => trigger_error('GuidePage::generateContent - what content!?')
};
*/
switch ($this->show)
{
case self::SHOW_NEW:
$this->displayNew();
break;
case self::SHOW_EDITOR:
$this->displayEditor();
break;
case self::SHOW_GUIDE:
$this->displayGuide();
break;
case self::SHOW_CHANGELOG:
$this->displayChangelog();
break;
default:
trigger_error('GuidePage::generateContent - what content!?');
}
}
private function displayNew() : void
{
// init required template vars
$this->editorFields = array(
'locale' => User::$localeId,
'status' => GUIDE_STATUS_DRAFT
);
}
private function displayEditor() : void
{
// can't check in init as subject is unknown
if ($this->subject->getField('status') == GUIDE_STATUS_ARCHIVED)
$this->notFound(Lang::guide('guide'), Lang::guide('notFound'));
$status = GUIDE_STATUS_NONE;
$rev = DB::Aowow()->selectCell('SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1', Type::GUIDE, $this->typeId);
$curStatus = DB::Aowow()->selectCell('SELECT `status` FROM ?_guides WHERE `id` = ?d ', $this->typeId);
if ($rev === null)
$rev = 0;
if ($this->save)
{
$rev++;
// insert Article
DB::Aowow()->query('INSERT INTO ?_articles (`type`, `typeId`, `locale`, `rev`, `editAccess`, `article`) VALUES (?d, ?d, ?d, ?d, ?d, ?)',
Type::GUIDE, $this->typeId, $this->_post['locale'], $rev, User::$groups, $this->_post['body']);
// link to Guide
$guideData = array(
'category' => $this->_post['category'],
'classId' => $this->_post['classId'],
'specId' => $this->_post['specId'],
'title' => $this->_post['title'],
'name' => $this->_post['name'],
'description' => $this->_post['description'] ?: Lang::trimTextClean((new Markup($this->_post['body']))->stripTags(), 120),
'locale' => $this->_post['locale'],
'roles' => User::$groups,
'status' => GUIDE_STATUS_DRAFT
);
DB::Aowow()->query('UPDATE ?_guides SET ?a WHERE `id` = ?d', $guideData, $this->typeId);
// new guide -> reload editor
if ($this->_get['id'] === 0)
header('Location: ?guide=edit&id='.$this->typeId, true, 302);
else
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `rev`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?d, ?)', $this->typeId, $rev, time(), User::$id, $this->_post['changelog']);
if ($this->_post['submit'])
{
$status = GUIDE_STATUS_REVIEW;
if ($curStatus != GUIDE_STATUS_REVIEW)
{
DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d WHERE `id` = ?d', GUIDE_STATUS_REVIEW, $this->typeId);
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $this->typeId, time(), User::$id, GUIDE_STATUS_REVIEW);
}
}
}
// init required template vars
$this->editorFields = array(
'category' => $this->_post['category'] ?? $this->subject->getField('category'),
'title' => $this->_post['title'] ?? $this->subject->getField('title'),
'name' => $this->_post['name'] ?? $this->subject->getField('name'),
'description' => $this->_post['description'] ?? $this->subject->getField('description'),
'text' => $this->_post['body'] ?? $this->subject->getArticle(),
'status' => $status ?: $this->subject->getField('status'),
'classId' => $this->_post['classId'] ?? $this->subject->getField('classId'),
'specId' => $this->_post['specId'] ?? $this->subject->getField('specId'),
'locale' => $this->_post['locale'] ?? $this->subject->getField('locale'),
'rev' => $rev
);
$this->extendGlobalData($this->subject->getJSGlobals());
}
private function displayGuide() : void
{
if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_QUICKFACTS))
{
$qf = [];
if ($this->subject->getField('cuFlags') & CC_FLAG_STICKY)
$qf[] = '[span class=guide-sticky]'.Lang::guide('sticky').'[/span]';
$qf[] = Lang::guide('author').Lang::main('colon').'[url=?user='.$this->subject->getField('author').']'.$this->subject->getField('author').'[/url]';
if ($this->subject->getField('category') == 1)
{
$c = $this->subject->getField('classId');
$s = $this->subject->getField('specId');
if ($c > 0)
{
$this->extendGlobalIds(Type::CHR_CLASS, $c);
$qf[] = Util::ucFirst(Lang::game('class')).Lang::main('colon').'[class='.$c.']';
}
if ($s > -1)
$qf[] = Lang::guide('spec').Lang::main('colon').'[icon class="c'.$c.' icontiny" name='.Game::$specIconStrings[$c][$s].']'.Lang::game('classSpecs', $c, $s).'[/icon]';
}
// $qf[] = Lang::guide('patch').Lang::main('colon').'3.3.5'; // replace with date
$qf[] = Lang::guide('added').Lang::main('colon').'[tooltip name=added]'.date('l, G:i:s', $this->subject->getField('date')).'[/tooltip][span class=tip tooltip=added]'.date(Lang::main('dateFmtShort'), $this->subject->getField('date')).'[/span]';
switch ($this->subject->getField('status'))
{
case GUIDE_STATUS_APPROVED:
$qf[] = Lang::guide('views').Lang::main('colon').'[n5='.$this->subject->getField('views').']';
if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_RATING))
{
$this->guideRating = array(
$this->subject->getField('rating'), // avg rating
User::canUpvote() && User::canDownvote() ? 'true' : 'false',
$this->subject->getField('_self'), // my rating amt; 0 = no vote
$this->typeId // guide Id
);
if ($this->subject->getField('nvotes') < 5)
$qf[] = Lang::guide('rating').Lang::main('colon').Lang::guide('noVotes');
else
$qf[] = Lang::guide('rating').Lang::main('colon').Lang::guide('votes', [round($this->rating['avg'], 1), $this->rating['n']]);
}
break;
case GUIDE_STATUS_ARCHIVED:
$qf[] = Lang::guide('status', GUIDE_STATUS_ARCHIVED);
break;
}
$qf = '[ul][li]'.implode('[/li][li]', $qf).'[/li][/ul]';
if ($this->subject->getField('status') == GUIDE_STATUS_REVIEW && User::isInGroup(U_GROUP_STAFF) && $this->_get['rev'])
{
$this->addScript([JS_STRING, '
DomContentLoaded.addEvent(function() {
let send = function (status)
{
let message = "";
let id = $WH.g_getGets().guide;
if (status == 4) // rejected
{
while (message === "")
message = prompt("Please provide your reasoning.");
if (message === null)
return false;
}
$.ajax({cache: false, url: "?admin=guide", type: "POST",
error: function() {
alert("Operation failed.");
},
success: function(json) {
if (json != 1)
alert("Operation failed.");
else
window.location.href = "?admin=guides";
},
data: { id: id, status: status, msg: message }
})
return true;
};
$WH.ge("btn-accept").onclick = send.bind(null, 3);
$WH.ge("btn-reject").onclick = send.bind(null, 4);
});
']);
$qf .= '[h3 style="text-align:center"]Admin[/h3]';
$qf .= '[div style="text-align:center"][url=# id="btn-accept" class=icon-tick]Approve[/url][url=# style="margin-left:20px" id="btn-reject" class=icon-delete]Reject[/url][/div]';
}
}
$this->redButtons[BUTTON_GUIDE_LOG] = true;
$this->redButtons[BUTTON_GUIDE_REPORT] = $this->subject->canBeReported();
$this->infobox = $qf ?? '';
$this->author = $this->subject->getField('author'); // add to g_pageInfo in GenericPage:prepareContent()
if ($this->subject->userCanView())
$this->redButtons[BUTTON_GUIDE_EDIT] = User::canWriteGuide() && $this->subject->getField('status') != GUIDE_STATUS_ARCHIVED;
// the article text itself is added by GenericPage::addArticle()
}
private function displayChangelog() : void
{
$this->addScript([JS_STRING, '
$(document).ready(function() {
var radios = $("input[type=radio]");
function limit(col, val) {
radios.each(function(i, e) {
if (col == e.name)
return;
if (col == "b")
e.disabled = (val <= parseInt(e.value));
else if (col == "a")
e.disabled = (val >= parseInt(e.value));
});
};
radios.each(function (i, e) {
e.onchange = limit.bind(this, e.name, parseInt(e.value));
if (i < 2 && e.name == "b") // first pair
$(e).trigger("click");
else if (e.value == 0 && e.name == "a") // last pair
$(e).trigger("click");
});
});
']);
$buff = '<ul>';
$inp = fn($rev) => User::isInGroup(U_GROUP_STAFF) ? ($rev !== null ? '<input name="a" value="'.$rev.'" type="radio"/><input name="b" value="'.$rev.'" type="radio"/><b>' : '<b style="margin-left:28px;">') : '';
$logEntries = DB::Aowow()->select('SELECT a.`displayName` 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->typeId);
foreach ($logEntries as $log)
{
if ($log['status'] != GUIDE_STATUS_NONE)
$buff .= '<li class="guide-changelog-status-change">'.$inp($log['rev']).Lang::guide('clStatusSet', [Lang::guide('status', $log['status'])]).Lang::main('colon').'</b>'.Util::formatTimeDiff($log['date'])."</li>\n";
else if ($log['msg'])
$buff .= '<li>'.$inp($log['rev']).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";
else
$buff .= '<li class="guide-changelog-minor-edit">'.$inp($log['rev']).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";
}
// append creation
$buff .= '<li class="guide-changelog-created">'.$inp(0).'<b>'.Lang::guide('clCreated').Lang::main('colon').'</b>'.Util::formatTimeDiff($this->subject->getField('date'))."</li>\n</ul>\n";
if (User::isInGroup(U_GROUP_STAFF))
$buff .= '<input type="button" value="Compare" onclick="alert(\'NYI\');"/>';
$this->name = lang::guide('clTitle', [$this->typeId, $this->subject->getField('title')]);
$this->extraHTML = $buff;
}
private function initNew() : void
{
$this->addScript(
[JS_FILE, 'article-description.js'],
[JS_FILE, 'article-editing.js'],
[JS_FILE, 'guide-editing.js'],
[JS_FILE, 'fileuploader.js'],
[JS_FILE, 'toolbar.js'],
[JS_FILE, 'AdjacentPreview.js'],
[CSS_FILE, 'article-editing.css'],
[CSS_FILE, 'fileuploader.css'],
[CSS_FILE, 'guide-edit.css'],
[CSS_FILE, 'AdjacentPreview.css'],
[CSS_STRING, '#upload-result input[type=text] { padding: 0px 2px; font-size: 12px; }'],
[CSS_STRING, '#upload-result > span { display:block; height: 22px; }'],
[CSS_STRING, '#upload-result { display: inline-block; text-align:right; }'],
[CSS_STRING, '#upload-progress { display: inline-block; margin-right:8px; }']
);
$this->articleUrl = 'new';
$this->tpl = 'guide-edit';
$this->name = Lang::guide('newTitle');
Lang::sort('guide', 'category');
$this->typeId = 0; // signals 'edit' to create new guide
}
private function initEdit() : bool
{
$this->addScript(
[JS_FILE, 'article-description.js'],
[JS_FILE, 'article-editing.js'],
[JS_FILE, 'guide-editing.js'],
[JS_FILE, 'fileuploader.js'],
[JS_FILE, 'toolbar.js'],
[JS_FILE, 'AdjacentPreview.js'],
[CSS_FILE, 'article-editing.css'],
[CSS_FILE, 'fileuploader.css'],
[CSS_FILE, 'guide-edit.css'],
[CSS_FILE, 'AdjacentPreview.css'],
[CSS_STRING, '#upload-result input[type=text] { padding: 0px 2px; font-size: 12px; }'],
[CSS_STRING, '#upload-result > span { display:block; height: 22px; }'],
[CSS_STRING, '#upload-result { display: inline-block; text-align:right; }'],
[CSS_STRING, '#upload-progress { display: inline-block; margin-right:8px; }']
);
$this->articleUrl = 'edit';
$this->tpl = 'guide-edit';
$this->name = Lang::guide('editTitle');
$this->save = $this->_post['save'] || $this->_post['submit'];
// reject inconsistent guide data
if ($this->save)
{
// req: set data
if (!$this->_post['title'] || !$this->_post['name'] || !$this->_post['body'] || $this->_post['locale'] === null)
return false;
// req: valid data
if (!in_array($this->_post['category'], $this->validCats) || !(CFG_LOCALES & (1 << $this->_post['locale'])))
return false;
// sanitize: spec / class
if ($this->_post['category'] == 1) // Classes
{
if ($this->_post['classId'] && !((1 << $this->_post['classId']) & CLASS_MASK_ALL))
$this->_post['classId'] = 0;
if (!in_array($this->_post['specId'], [-1, 0, 1, 2]))
$this->_post['specId'] = -1;
if ($this->_post['specId'] > -1 && !$this->_post['classId'])
$this->_post['specId'] = -1;
}
else
{
$this->_post['classId'] = 0;
$this->_post['specId'] = -1;
}
}
if ($this->_get['id']) // edit existing guide
{
$this->typeId = $this->_get['id']; // just to display sensible not-found msg
if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `id` = ?d AND `status` <> ?d {AND `userId` = ?d}', $this->typeId, GUIDE_STATUS_ARCHIVED, User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : User::$id))
$this->typeId = intVal($id);
}
else if ($this->_get['id'] === 0) // create new guide and load in editor
$this->typeId = DB::Aowow()->query('INSERT INTO ?_guides (`userId`, `date`, `status`) VALUES (?d, ?d, ?d)', User::$id, time(), GUIDE_STATUS_DRAFT);
return $this->typeId > 0;
}
protected function editorFields(string $field, bool $asInt = false) : string|int
{
return $this->editorFields[$field] ?? ($asInt ? 0 : '');
}
protected function generateTooltip()
{
$power = new StdClass();
if (!$this->subject->error)
{
$power->{'name_'.User::$localeString} = $this->name;
$power->{'tooltip_'.User::$localeString} = $this->subject->renderTooltip();
}
return sprintf($this->powerTpl, Util::toJSON($this->articleUrl ?: $this->typeId), User::$localeId, Util::toJSON($power, JSON_AOWOW_POWER));
}
protected function generatePath() : void
{
if ($x = $this->subject?->getField('category'))
$this->path[] = $x;
}
protected function generateTitle() : void
{
if ($this->show == self::SHOW_EDITOR)
array_unshift($this->title, Lang::guide('editTitle').Lang::main('colon').$this->subject->getField('title'), Lang::guide('guides'));
if ($this->show == self::SHOW_NEW)
array_unshift($this->title, Lang::guide('newTitle'), Lang::guide('guides'));
else
array_unshift($this->title, $this->subject->getField('title'), Lang::guide('guides'));
}
protected function postCache() : void
{
// increment views of published guide; ignore caching
if ($this->subject?->getField('status') == GUIDE_STATUS_APPROVED)
DB::Aowow()->query('UPDATE ?_guides SET `views` = `views` + 1 WHERE `id` = ?d', $this->typeId);
}
}
?>

100
pages/guides.php Normal file
View File

@@ -0,0 +1,100 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// menuId ?:Category g_initPath()
// tabid 6:Guides g_initHeader()
class GuidesPage extends GenericPage
{
use TrListPage;
protected $type = Type::Guide;
protected $tpl = 'list-page-generic';
protected $path = [6];
protected $tabId = 6;
protected $mode = CACHE_TYPE_PAGE;
protected $validCats = [null, 1, 2, 3, 4, 5, 6, 7, 8, 9];
private $myGuides = false;
public function __construct($pageCall, $pageParam)
{
$this->getCategoryFromUrl($pageParam);
parent::__construct($pageCall, $pageParam);
if ($pageCall == 'my-guides')
{
if (!User::$id)
$this->error();
$this->name = Util::ucFirst(Lang::guide('myGuides'));
$this->myGuides = true;
}
else
$this->name = Util::ucFirst(Lang::guide('guides'));
}
protected function generateContent()
{
$hCols = ['patch']; // pointless: display date instead
$vCols = [];
$xCols = ['$Listview.extraCols.date']; // ok
if ($this->myGuides)
{
$conditions = [['userId', User::$id]];
$hCols[] = 'author';
$vCols[] = 'status';
}
else
{
$conditions = array(
['locale', User::$localeId],
['status', GUIDE_STATUS_ARCHIVED, '!'], // never archived guides
[
'OR',
['status', GUIDE_STATUS_APPROVED], // currently approved
['rev', 0, '>'] // has previously approved revision
]
);
if (isset($this->category[0]))
$conditions[] = ['category', $this->category];
}
$data = [];
$guides = new GuideList($conditions);
if (!$guides->error)
$data = array_values($guides->getListviewData());
$tabData = array(
'data' => $data,
'name' => Util::ucFirst(Lang::guide('guides')),
'hiddenCols' => $hCols,
'visibleCols' => $vCols,
'extraCols' => $xCols
);
$this->lvTabs[] = [GuideList::$brickFile, $tabData];
$this->redButtons = [BUTTON_GUIDE_NEW => User::$id && User::canComment()];
}
protected function generateTitle()
{
array_unshift($this->title, $this->name);
if (isset($this->category[0]))
array_unshift($this->title, Lang::guide('category', $this->category[0]));
}
protected function generatePath()
{
if (isset($this->category[0]))
$this->path[] = $this->category[0];
}
}
?>

View File

@@ -12,14 +12,19 @@ class GuildPage extends GenericPage
protected $lvTabs = [];
protected $type = Type::GUILD;
protected $tabId = 1;
protected $path = [1, 5, 2];
protected $tpl = 'roster';
protected $js = ['profile_all.js', 'profile.js'];
protected $css = [['path' => 'Profiler.css']];
protected $js = [[JS_FILE, 'profile_all.js'], [JS_FILE, 'profile.js']];
protected $css = [[CSS_FILE, 'Profiler.css']];
public function __construct($pageCall, $pageParam)
{
if (!CFG_PROFILER_ENABLE)
$this->error();
$params = array_map('urldecode', explode('.', $pageParam));
if ($params[0])
$params[0] = Profiler::urlize($params[0]);
@@ -92,7 +97,7 @@ class GuildPage extends GenericPage
if ($this->doResync)
return;
$this->addJS('?data=realms.weight-presets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=realms.weight-presets&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$this->redButtons[BUTTON_RESYNC] = [$this->subjectGUID, 'guild'];
@@ -104,7 +109,7 @@ class GuildPage extends GenericPage
// statistic calculations here
// smuggle the guild ranks into the html
if ($ranks = DB::Aowow()->selectCol('SELECT rank AS ARRAY_KEY, name FROM ?_profiler_guild_rank WHERE guildId = ?d', $this->subjectGUID))
if ($ranks = DB::Aowow()->selectCol('SELECT `rank` AS ARRAY_KEY, name FROM ?_profiler_guild_rank WHERE guildId = ?d', $this->subjectGUID))
$this->extraHTML = '<script type="text/javascript">var guild_ranks = '.Util::toJSON($ranks).';</script>';
@@ -113,7 +118,7 @@ class GuildPage extends GenericPage
/**************/
// tab: members
$member = new LocalProfileList(array(['p.guild', $this->subjectGUID]));
$member = new LocalProfileList(array(['p.guild', $this->subjectGUID], CFG_SQL_LIMIT_NONE));
if (!$member->error)
{
$this->lvTabs[] = ['profile', array(
@@ -125,15 +130,15 @@ class GuildPage extends GenericPage
}
}
public function notFound($title = '', $msg = '')
public function notFound(string $title = '', string $msg = '') : void
{
return parent::notFound($title ?: Util::ucFirst(Lang::profiler('profiler')), $msg ?: Lang::profiler('notFound', 'guild'));
parent::notFound($title ?: Util::ucFirst(Lang::profiler('profiler')), $msg ?: Lang::profiler('notFound', 'guild'));
}
private function handleIncompleteData($teamGuid)
{
//display empty page and queue status
$newId = Profiler::scheduleResync(TYPE_GUILD, $this->realmId, $teamGuid);
$newId = Profiler::scheduleResync(Type::GUILD, $this->realmId, $teamGuid);
$this->doResync = ['guild', $newId];
$this->initialSync();

View File

@@ -10,13 +10,20 @@ class GuildsPage extends GenericPage
{
use TrProfiler;
protected $type = Type::GUILD;
protected $tabId = 1;
protected $path = [1, 5, 2];
protected $tpl = 'guilds';
protected $js = ['filters.js', 'profile_all.js', 'profile.js'];
protected $js = [[JS_FILE, 'filters.js'], [JS_FILE, 'profile_all.js'], [JS_FILE, 'profile.js']];
protected $_get = ['filter' => ['filter' => FILTER_UNSAFE_RAW]];
public function __construct($pageCall, $pageParam)
{
if (!CFG_PROFILER_ENABLE)
$this->error();
$this->getSubjectFromUrl($pageParam);
$this->filterObj = new GuildListFilter();
@@ -50,7 +57,7 @@ class GuildsPage extends GenericPage
protected function generateContent()
{
$this->addJS('?data=realms&locale='.User::$localeId.'&t='.$_SESSION['dataKey']);
$this->addScript([JS_FILE, '?data=realms&locale='.User::$localeId.'&t='.$_SESSION['dataKey']]);
$conditions = array(
['c.deleteInfos_Account', null],
@@ -62,7 +69,7 @@ class GuildsPage extends GenericPage
// recreate form selection
$this->filter = $this->filterObj->getForm();
$this->filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : null;
$this->filter['query'] = $this->_get['filter'];
$this->filter['initData'] = ['type' => 'guilds'];
$tabData = array(

View File

@@ -7,11 +7,12 @@ if (!defined('AOWOW_REVISION'))
class HomePage extends GenericPage
{
protected $tpl = 'home';
protected $js = ['home.js'];
protected $css = [['path' => 'home.css']];
protected $js = [[JS_FILE, 'home.js']];
protected $css = [[CSS_FILE, 'home.css']];
protected $featuredBox = [];
protected $oneliner = '';
protected $homeTitle = '';
public function __construct()
{
@@ -20,7 +21,7 @@ class HomePage extends GenericPage
protected function generateContent()
{
$this->addCSS(['string' => '.announcement { margin: auto; max-width: 1200px; padding: 0px 15px 15px 15px }']);
$this->addScript([CSS_STRING, '.announcement { margin: auto; max-width: 1200px; padding: 0px 15px 15px 15px }']);
// load oneliner
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_home_oneliner WHERE active = 1 LIMIT 1'))
@@ -52,8 +53,8 @@ class HomePage extends GenericPage
protected function generateTitle()
{
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_home_titles WHERE active = 1 AND title_loc?d <> "" ORDER BY RAND() LIMIT 1', User::$localeId))
$this->title[0] .= Lang::main('colon').Util::localizedString($_, 'title');
if ($_ = DB::Aowow()->selectCell('SELECT title FROM ?_home_titles WHERE active = 1 AND locale = ?d ORDER BY RAND() LIMIT 1', User::$localeId))
$this->homeTitle = CFG_NAME.Lang::main('colon').$_;
}
protected function generatePath() {}

View File

@@ -8,9 +8,9 @@ if (!defined('AOWOW_REVISION'))
// tabId 0: Database g_initHeader()
class IconPage extends GenericPage
{
use DetailPage;
use TrDetailPage;
protected $type = TYPE_ICON;
protected $type = Type::ICON;
protected $typeId = 0;
protected $tpl = 'icon';
protected $path = [0, 31];
@@ -25,11 +25,11 @@ class IconPage extends GenericPage
$this->subject = new IconList(array(['id', $this->typeId]));
if ($this->subject->error)
$this->notFound(Util::ucFirst(Lang::game('icon')), Lang::icon('notFound'));
$this->notFound(Lang::game('icon'), Lang::icon('notFound'));
$this->extendGlobalData($this->subject->getJSGlobals());
$this->name = Util::ucFirst($this->subject->getField('name'));
$this->name = $this->subject->getField('name');
$this->icon = $this->subject->getField('name', true, true);
}

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