Begin Refactor

This commit is contained in:
color.diff=auto 2021-03-21 13:47:29 -06:00
parent 7622dc0d02
commit 018931d7ae
197 changed files with 47799 additions and 6 deletions

204
Armor.txt Normal file
View File

@ -0,0 +1,204 @@
name version compactsave rarity spawnable minac maxac absorbs speed reqstr block durability nodurability level levelreq cost gamble cost code namestr magic lvl auto prefix alternategfx OpenBetaGfx normcode ubercode ultracode spelloffset component invwidth invheight hasinv gemsockets gemapplytype flippyfile invfile uniqueinvfile setinvfile rArm lArm Torso Legs rSPad lSPad useable throwable stackable minstack maxstack type type2 dropsound dropsfxframe usesound unique transparent transtbl quivered lightradius belt quest missiletype durwarning qntwarning mindam maxdam StrBonus DexBonus gemoffset bitfield1 CharsiMin CharsiMax CharsiMagicMin CharsiMagicMax CharsiMagicLvl GheedMin GheedMax GheedMagicMin GheedMagicMax GheedMagicLvl AkaraMin AkaraMax AkaraMagicMin AkaraMagicMax AkaraMagicLvl FaraMin FaraMax FaraMagicMin FaraMagicMax FaraMagicLvl LysanderMin LysanderMax LysanderMagicMin LysanderMagicMax LysanderMagicLvl DrognanMin DrognanMax DrognanMagicMin DrognanMagicMax DrognanMagicLvl HraltiMin HraltiMax HraltiMagicMin HraltiMagicMax HraltiMagicLvl AlkorMin AlkorMax AlkorMagicMin AlkorMagicMax AlkorMagicLvl OrmusMin OrmusMax OrmusMagicMin OrmusMagicMax OrmusMagicLvl ElzixMin ElzixMax ElzixMagicMin ElzixMagicMax ElzixMagicLvl AshearaMin AshearaMax AshearaMagicMin AshearaMagicMax AshearaMagicLvl CainMin CainMax CainMagicMin CainMagicMax CainMagicLvl HalbuMin HalbuMax HalbuMagicMin HalbuMagicMax HalbuMagicLvl JamellaMin JamellaMax JamellaMagicMin JamellaMagicMax JamellaMagicLvl LarzukMin LarzukMax LarzukMagicMin LarzukMagicMax LarzukMagicLvl MalahMin MalahMax MalahMagicMin MalahMagicMax MalahMagicLvl DrehyaMin DrehyaMax DrehyaMagicMin DrehyaMagicMax DrehyaMagicLvl Source Art Game Art Transform InvTrans SkipName NightmareUpgrade HellUpgrade mindam maxdam nameable
Cap/hat 0 0 1 1 3 5 0 0 0 0 10 0 1 0 0 0 cap cap cap cap cap xap uap 0 0 2 2 1 3 1 flpcap invcap invcapu invcapu 0 0 0 0 0 helm item_cap 12 item_cap 0 0 5 0 0 0 0 0 3 0 0 0 0 1 2 8 0 skp ghm 0 0 1
Skull Cap 0 0 4 1 8 11 0 0 15 0 14 0 5 0 0 0 skp skp skp skp skp xkp ukp 0 0 2 2 1 3 1 flpskp invskp 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 hlm crn 0 0 1
Helm 0 0 4 1 15 18 0 0 26 0 19 0 11 0 0 0 hlm hlm hlm hlm hlm xlm ulm 0 0 2 2 1 3 1 flphlm invhlm invhlmu invhlmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 fhl ghm 0 0 1
Full Helm 0 0 4 1 23 26 0 0 41 0 24 0 15 0 0 0 fhl fhl fhl hlm fhl xhl uhl 0 0 2 2 1 3 1 flpfhl invfhl invfhlu invfhlu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 0 8 0 ghm xxx 0 0 1
Great Helm 0 0 4 1 30 35 0 0 63 0 32 0 23 0 0 0 ghm ghm ghm hlm ghm xhm uhm 0 0 2 2 1 4 1 flpghm invghm 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 7 8 0 crn xxx 0 0 1
Crown 0 0 4 1 25 45 0 0 55 0 40 0 29 0 0 0 crn crn crn hlm crn xrn urn 0 0 2 2 1 4 1 flpcrn invcrn 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Mask 0 0 4 1 9 27 0 0 23 0 16 0 19 0 0 0 msk msk msk hlm msk xsk usk 0 0 2 2 1 4 1 flpmsk invmsk 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Quilted Armor 0 0 1 1 8 11 0 0 12 0 16 0 1 0 0 0 qui qui qlt qlt qui xui uui 0 1 2 3 1 3 1 flpqlt invqlt 0 0 0 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 7 8 0 lea gth 0 0 1
Leather Armor 0 0 2 1 14 17 0 0 15 0 19 0 3 0 0 0 lea lea lea lea lea xea uea 0 1 2 3 1 3 1 flplea invlea 0 0 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 7 8 0 hla ful 0 0 1
Hard Leather Armor 0 0 3 1 21 24 0 0 20 0 22 0 5 0 0 0 hla hla hla hla hla xla ula 0 1 2 3 1 3 1 flphla invhla 1 1 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 7 8 0 stu aar 0 0 1
Studded Leather 0 0 4 1 32 35 0 0 27 0 26 0 8 0 0 0 stu stu stu stu stu xtu utu 0 1 2 3 1 3 1 flpstu invstu 1 0 0 1 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 7 8 0 rng ltp 0 0 1
Ring Mail 0 0 4 1 45 48 0 5 36 0 21 0 11 0 0 0 rng rng rng rng rng xng ung 0 1 2 3 1 4 1 flprng invrng 0 0 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 scl chn 0 0 1
Scale Mail 0 0 4 1 57 60 0 10 44 0 29 0 13 0 0 0 scl scl scl scl scl xcl ucl 0 1 2 3 1 3 1 flpscl invscl 1 1 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 chn brs 0 0 1
Chain Mail 0 0 4 1 72 75 0 5 48 0 36 0 15 0 0 0 chn chn chn chn chn xhn uhn 0 1 2 3 1 3 1 flpchn invchn 1 1 1 1 2 2 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 brs spl 0 0 1
Breast Plate 0 0 4 1 65 68 1 0 30 0 40 0 18 0 0 0 brs brs brs brs brs xrs urs 0 1 2 3 1 4 1 flpbrs invbrs 0 0 2 0 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 spl plt 0 0 1
Splint Mail 0 0 4 1 90 95 0 5 51 0 24 0 20 0 0 0 spl spl spl spl spl xpl upl 0 1 2 3 1 3 1 flpspl invspl 1 1 2 1 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 plt fld 0 0 1
Plate Mail 0 0 4 1 108 116 0 10 65 0 48 0 24 0 0 0 plt plt plt plt plt xlt ult 0 1 2 3 1 3 1 flpplt invplt 2 2 2 2 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 fld xxx 0 0 1
Field Plate 0 0 4 1 101 105 2 5 55 0 38 0 28 0 0 0 fld fld fld fld fld xld uld 0 1 2 3 1 3 1 flpfld invfld 1 1 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 gth ful 0 0 1
Gothic Plate 0 0 4 1 128 135 0 5 70 0 44 0 32 0 0 0 gth gth gth gth gth xth uth 0 1 2 3 1 5 1 flpgth invgth 2 2 1 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 ful xxx 0 0 1
Full Plate Mail 0 0 4 1 150 161 2 10 80 0 56 0 37 0 0 0 ful ful ful ful ful xul uul 0 1 2 3 1 5 1 flpful invful invfulu invfulu 2 2 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 aar xxx 0 0 1
Ancient Armor 0 0 4 1 218 233 5 5 100 0 48 0 40 0 0 0 aar aar aar aar aar xar uar 0 1 2 3 1 5 1 flpaar invaar invaaru invaaru 1 2 2 2 2 0 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 ltp xxx 0 0 1
Light Plate 0 0 4 1 90 107 1 0 41 0 48 0 35 0 0 0 ltp ltp ltp ltp ltp xtp utp 0 1 2 3 1 4 1 flpltp invltp 2 0 1 1 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 buc xxx 0 0 1
Buckler 0 0 2 1 4 6 0 0 12 0 10 0 1 0 0 0 buc buc buc buc buc xuc uuc 0 7 2 2 1 2 2 flpbuc invbuc invbucu invbucu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 1 3 100 0 3 8 8 0 sml tow 0 0 1
Small Shield 0 0 3 1 8 10 0 0 22 5 13 0 5 0 0 0 sml sml buc buc sml xml uml 0 7 2 2 1 3 2 flpsml invsml invsmlu invsmlu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 2 3 100 0 3 8 5 0 lrg kit 0 0 1
Large Shield 0 0 4 1 12 14 0 5 34 12 19 0 11 0 0 0 lrg lrg lrg buc lrg xrg urg 0 7 2 3 1 4 2 flplrg invlrg invlrgu invlrgu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 2 4 100 0 3 8 2 0 kit tow 0 0 1
Kite Shield 0 0 4 1 16 18 0 0 47 8 24 0 15 0 0 0 kit kit kit buc kit xit uit 0 7 2 3 1 4 2 flpkit invkit invkitu invkitu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 2 5 100 0 3 8 2 0 tow gts 0 0 1
Tower Shield 0 0 4 1 22 25 1 10 75 24 48 0 22 0 0 0 tow tow tow buc tow xow uow 0 7 2 3 1 4 2 flptow invtow invtowu invtowu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 1 5 100 0 3 8 2 0 gts xxx 0 0 1
Gothic Shield 0 0 4 1 30 35 0 5 60 16 32 0 30 0 0 0 gts gts kit buc gts xts uts 0 7 2 4 1 4 2 flpgts invgts invgtsu invgtsu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 2 6 100 0 3 8 2 0 xxx xxx 0 0 1
Gloves(L) 0 0 1 1 2 3 0 0 0 0 10 0 3 0 0 0 lgl lgl lgl lgl lgl xlg ulg 0 16 2 2 1 4 0 flplgl invlgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 vgl hgl 0 0 1
Heavy Gloves 0 0 1 1 5 6 0 0 0 0 11 0 7 0 0 0 vgl vgl vgl vgl vgl xvg uvg 0 16 2 2 1 4 0 flpvgl invvgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 mgl tgl 0 0 1
Bracers(M) 0 0 2 1 8 9 0 0 25 0 13 0 12 0 0 0 mgl mgl mgl mgl mgl xmg umg 0 16 2 2 1 4 0 flpmgl invmgl 0 0 0 0 0 glov item_gloveschain 12 item_gloveschain 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 tgl hgl 0 0 1
Light Gauntlets 0 0 3 1 9 11 0 0 45 0 14 0 20 0 0 0 tgl tgl mgl mgl tgl xtg utg 0 16 2 2 1 4 0 flptgl invtgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Gaunlets(H) 0 0 4 1 12 15 0 0 60 0 19 0 27 0 0 0 hgl hgl hgl hgl hgl xhg uhg 0 16 2 2 1 4 0 flphgl invhgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Leather Boots 0 0 1 1 2 3 0 0 0 0 10 0 3 0 0 0 lbt lbt lbt lbt lbt xlb ulb 0 16 2 2 1 4 1 flplbt invlbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 3 8 120 0 1 0 8 0 vbt tbt 0 0 1
Heavy Boots 0 0 1 1 5 6 0 0 18 0 11 0 7 0 0 0 vbt vbt vbt vbt vbt xvb uvb 0 16 2 2 1 4 1 flpvbt invvbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 4 10 120 0 1 0 8 0 mbt tbt 0 0 1
Chain Boots 0 0 2 1 8 9 0 0 30 0 13 0 12 0 0 0 mbt mbt mbt mbt mbt xmb umb 0 16 2 2 1 4 1 flpmbt invmbt 0 0 0 0 0 boot item_bootschain 12 item_bootschain 0 0 5 0 0 0 0 0 6 0 6 12 120 0 3 0 8 0 tbt hbt 0 0 1
Light Plate Boots 0 0 3 1 9 11 0 0 50 0 14 0 20 0 0 0 tbt tbt mbt mbt tbt xtb utb 0 16 2 2 1 4 1 flptbt invtbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 8 16 120 0 3 0 8 0 xxx xxx 0 0 1
Plate Boots 0 0 4 1 12 15 0 0 70 0 19 0 27 0 0 0 hbt hbt hbt hbt hbt xhb uhb 0 16 2 2 1 4 1 flphbt invhbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 10 20 120 0 3 0 8 0 xxx xxx 0 0 1
Sash(L) 0 0 1 1 2 2 0 0 0 0 10 0 3 0 0 0 lbl lbl lbl lbl lbl zlb ulc 0 16 2 1 1 1 0 flplbl invlbl 0 0 0 0 0 belt item_lightarmor 12 item_lightarmor 0 0 5 0 0 1 0 0 4 0 0 0 0 1 0 8 0 vbl tbl 0 0 1
Light Belt 0 0 1 1 3 3 0 0 0 0 11 0 7 0 0 0 vbl vbl vbl vbl vbl zvb uvc 0 16 2 1 1 1 0 flpvbl invvbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 4 0 0 4 0 0 0 0 1 0 8 0 mbl hbl 0 0 1
Belt(M) 0 0 2 1 5 5 0 0 25 0 13 0 12 0 0 0 mbl mbl mbl mbl mbl zmb umc 0 16 2 1 1 1 0 flpmbl invmbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 0 0 0 4 0 0 0 0 1 0 8 0 tbl hbl 0 0 1
Heavy Belt 0 0 2 1 6 6 0 0 45 0 14 0 20 0 0 0 tbl tbl mbl mbl tbl ztb utc 0 16 2 1 1 1 0 flptbl invtbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 5 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Girdle(H) 0 0 3 1 8 11 0 0 60 0 19 0 27 0 0 0 hbl hbl hbl hbl hbl zhb uhc 0 16 2 1 1 1 0 flphbl invhbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 3 0 0 4 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Bone Helm 0 0 2 1 33 36 0 0 25 0 32 0 22 0 0 0 bhm bhm bhm hlm bhm xh9 uh9 0 0 2 2 1 3 1 flpbhm invbhm invbhmu invbhmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 2 0 0 0 3 0 0 0 0 1 7 8 0 xxx xxx 0 0 1
Bone Shield 0 0 2 1 10 30 0 0 25 20 32 0 19 0 0 0 bsh bsh bsh buc bsh xsh ush 0 7 2 3 1 3 2 flpbsh invbsh invbshu invbshu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 3 6 100 0 1 8 8 0 xxx xxx 0 0 1
Spiked Shield 0 0 3 1 15 25 0 0 30 10 32 0 11 0 0 0 spk spk spk buc spk xpk upk 0 7 2 3 1 3 2 flpspk invspk invspku invspku 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 5 9 100 0 1 8 8 0 xxx xxx 0 0 1
War Hat 0 0 1 1 45 53 0 0 20 0 10 0 34 22 0 0 xap xap cap cap cap xap uap 0 0 2 2 1 3 1 flpcap invcap 0 0 0 0 0 helm item_cap 12 item_cap 0 0 5 0 0 0 0 0 3 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Sallet 0 0 4 1 52 62 0 0 43 0 14 0 37 25 0 0 xkp xkp skp skp skp xkp ukp 0 0 2 2 1 3 1 flpskp invskp invxkpu invxkpu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Casque 0 0 4 1 63 72 0 0 59 0 19 0 42 25 0 0 xlm xlm hlm hlm hlm xlm ulm 0 0 2 2 1 3 1 flphlm invhlm invhlmu invhlmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Basinet 0 0 4 1 75 84 0 0 82 0 24 0 45 25 0 0 xhl xhl fhl fhl fhl xhl uhl 0 0 2 2 1 3 1 flpfhl invfhl invfhlu invfhlu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Winged Helm 0 0 4 1 85 98 0 0 115 0 32 0 51 25 0 0 xhm xhm ghm ghm ghm xhm uhm 0 0 2 2 1 4 1 flpghm invghm 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 7 8 0 xxx xxx 0 0 1
Grand Crown 0 0 4 1 78 113 0 0 103 0 40 0 55 25 0 0 xrn xrn crn crn crn xrn urn 0 0 2 2 1 4 1 flpcrn invcrn invxrnu invxrnu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Death Mask 0 0 4 1 54 86 0 0 55 0 16 0 48 25 0 0 xsk xsk msk msk msk xsk usk 0 0 2 2 1 4 1 flpmsk invmsk 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Ghost Armor 0 0 1 1 102 117 0 0 38 0 16 0 34 22 0 0 xui xui qlt qlt qui xui uui 0 1 2 3 1 3 1 flpqlt invqlt 0 0 0 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Serpentskin Armor 0 0 2 1 111 126 0 0 43 0 19 0 36 24 0 0 xea xea lea lea lea xea uea 0 1 2 3 1 3 1 flplea invlea 0 0 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Demonhide Armor 0 0 3 1 122 136 0 0 50 0 22 0 37 25 0 0 xla xla hla hla hla xla ula 0 1 2 3 1 3 1 flphla invhla 1 1 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Trellised Armor 0 0 4 1 138 153 0 0 61 0 26 0 40 25 0 0 xtu xtu stu stu stu xtu utu 0 1 2 3 1 3 1 flpstu invstu invxtuu invxtuu 1 0 0 1 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Linked Mail 0 0 4 1 158 172 0 5 74 0 21 0 42 25 0 0 xng xng rng rng rng xng ung 0 1 2 3 1 4 1 flprng invrng 0 0 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Tigulated Mail 0 0 4 1 176 190 0 10 86 0 29 0 43 25 0 0 xcl xcl scl scl scl xcl ucl 0 1 2 3 1 4 1 flpscl invscl 1 1 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Mesh Armor 0 0 4 1 198 213 0 5 92 0 36 0 45 25 0 0 xhn xhn chn chn chn xhn uhn 0 1 2 3 1 4 1 flpchn invchn 1 1 1 1 2 2 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Cuirass 0 0 4 1 188 202 1 0 65 0 40 0 47 25 0 0 xrs xrs brs brs brs xrs urs 0 1 2 3 1 4 1 flpbrs invbrs invxrss 0 0 2 0 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Russet Armor 0 0 4 1 225 243 0 5 97 0 24 0 49 25 0 0 xpl xpl spl spl spl xpl upl 0 1 2 3 1 4 1 flpspl invspl 1 1 2 1 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Templar Coat 0 0 4 1 252 274 0 10 118 0 48 0 52 25 0 0 xlt xlt plt plt plt xlt ult 0 1 2 3 1 4 1 flpplt invplt 2 2 2 2 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Sharktooth Armor 0 0 4 1 242 258 2 5 103 0 38 0 55 25 0 0 xld xld fld fld fld xld uld 0 1 2 3 1 4 1 flpfld invfld 1 1 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Embossed Plate 0 0 4 1 282 303 0 5 125 0 44 0 58 25 0 0 xth xth gth gth gth xth uth 0 1 2 3 1 5 1 flpgth invgth 2 2 1 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Chaos Armor 0 0 4 1 315 342 2 10 140 0 56 0 61 25 0 0 xul xul ful ful ful xul uul 0 1 2 3 1 5 1 flpful invful 2 2 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Ornate Armor 0 0 4 1 417 450 5 5 170 0 48 0 64 25 0 0 xar xar aar aar aar xar uar 0 1 2 3 1 5 1 flpaar invaar invxaru invxaru 1 2 2 2 2 0 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Mage Plate 0 0 4 1 225 261 1 0 55 0 48 0 60 25 0 0 xtp xtp ltp ltp ltp xtp utp 0 1 2 3 1 4 1 flpltp invltp 2 0 1 1 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 xxx xxx 0 0 1
Defender 0 0 2 1 41 49 0 0 38 10 54 0 34 22 0 0 xuc xuc buc buc buc xuc uuc 0 7 2 2 1 2 2 flpbuc invbuc invbucu invbucu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 8 12 100 0 3 8 8 0 xxx xxx 0 0 1
Round Shield 0 0 3 1 47 55 0 0 53 12 51 0 37 25 0 0 xml xml buc buc sml xml uml 0 7 2 2 1 3 2 flpsml invsml invxmlu invxmlu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 7 14 100 0 3 8 5 0 xxx xxx 0 0 1
Scutum 0 0 4 1 53 61 0 5 71 14 50 0 42 25 0 0 xrg xrg lrg lrg lrg xrg urg 0 7 2 3 1 4 2 flplrg invlrg invxrgu invxrgu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 11 15 100 0 3 8 2 0 xxx xxx 0 0 1
Dragon Shield 0 0 4 1 59 67 0 0 91 18 61 0 45 25 0 0 xit xit kit kit kit xit uit 0 7 2 3 1 4 2 flpkit invkit invkitu invkitu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 15 24 100 0 3 8 2 0 xxx xxx 0 0 1
Pavise 0 0 4 1 68 78 1 10 133 24 58 0 50 25 0 0 xow xow tow tow tow xow uow 0 7 2 3 1 4 2 flptow invtow invtowu invtowu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 10 17 100 0 3 8 2 0 xxx xxx 0 0 1
Ancient Shield 0 0 4 1 80 93 0 5 110 16 64 0 56 25 0 0 xts xts kit kit gts xts uts 0 7 2 4 1 4 2 flpgts invgts invgtsu invgtsu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 12 16 100 0 3 8 2 0 xxx xxx 0 0 1
Demonhide Gloves 0 0 1 1 28 35 0 0 20 0 10 0 33 21 0 0 xlg xlg lgl lgl lgl xlg ulg 0 16 2 2 1 4 0 flplgl invlgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Sharkskin Gloves 0 0 1 1 33 39 0 0 20 0 11 0 39 25 0 0 xvg xvg vgl vgl vgl xvg uvg 0 16 2 2 1 4 0 flpvgl invvgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Heavy Bracers 0 0 2 1 37 44 0 0 58 0 13 0 43 25 0 0 xmg xmg mgl mgl mgl xmg umg 0 16 2 2 1 4 0 flpmgl invmgl 0 0 0 0 0 glov item_gloveschain 12 item_gloveschain 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Battle Gauntlets 0 0 3 1 39 47 0 0 88 0 14 0 49 25 0 0 xtg xtg mgl mgl tgl xtg utg 0 16 2 2 1 4 0 flptgl invtgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
War Gauntlets 0 0 4 1 43 53 0 0 110 0 19 0 54 25 0 0 xhg xhg hgl hgl hgl xhg uhg 0 16 2 2 1 4 0 flphgl invhgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Demonhide Boots 0 0 1 1 28 35 0 0 20 0 10 0 36 24 0 0 xlb xlb lbt lbt lbt xlb ulb 0 16 2 2 1 4 1 flplbt invlbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 26 46 120 0 1 0 8 0 xxx xxx 0 0 1
Sharkskin Boots 0 0 1 1 33 39 0 0 47 0 11 0 39 25 0 0 xvb xvb vbt vbt vbt xvb uvb 0 16 2 2 1 4 1 flpvbt invvbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 28 50 120 0 1 0 8 0 xxx xxx 0 0 1
Mesh Boots 0 0 2 1 37 44 0 0 65 0 13 0 43 25 0 0 xmb xmb mbt mbt mbt xmb umb 0 16 2 2 1 4 1 flpmbt invmbt 0 0 0 0 0 boot item_bootschain 12 item_bootschain 0 0 5 0 0 0 0 0 6 0 23 52 120 0 3 0 8 0 xxx xxx 0 0 1
Battle Boots 0 0 3 1 39 47 0 0 95 0 14 0 49 25 0 0 xtb xtb mbt mbt tbt xtb utb 0 16 2 2 1 4 1 flptbt invtbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 37 64 120 0 3 0 8 0 xxx xxx 0 0 1
War Boots 0 0 4 1 43 53 0 0 125 0 19 0 54 25 0 0 xhb xhb hbt hbt hbt xhb uhb 0 16 2 2 1 4 1 flphbt invhbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 39 80 120 0 3 0 8 0 xxx xxx 0 0 1
Demonhide Sash 0 0 1 1 29 34 0 0 20 0 10 0 36 24 0 0 zlb zlb lbl lbl lbl zlb ulc 0 16 2 1 1 1 0 flplbl invlbl 0 0 0 0 0 belt item_lightarmor 12 item_lightarmor 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Sharkskin Belt 0 0 1 1 31 36 0 0 20 0 11 0 39 25 0 0 zvb zvb vbl vbl vbl zvb uvc 0 16 2 1 1 1 0 flpvbl invvbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Mesh Belt 0 0 2 1 35 40 0 0 58 0 13 0 43 25 0 0 zmb zmb mbl mbl mbl zmb umc 0 16 2 1 1 1 0 flpmbl invmbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Battle Belt 0 0 2 1 37 42 0 0 88 0 14 0 49 25 0 0 ztb ztb mbl mbl tbl ztb utc 0 16 2 1 1 1 0 flptbl invtbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
War Belt 0 0 3 1 41 52 0 0 110 0 19 0 54 25 0 0 zhb zhb hbl hbl hbl zhb uhc 0 16 2 1 1 1 0 flphbl invhbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Grim Helm 0 0 2 1 60 125 0 0 58 0 32 0 50 25 0 0 xh9 xh9 bhm bhm bhm xh9 uh9 0 0 2 2 1 3 1 flpbhm invbhm invbhmu invbhmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 2 0 0 0 3 0 0 0 0 1 7 8 0 xxx xxx 0 0 1
Grim Shield 0 0 2 1 50 150 0 0 58 20 56 0 48 25 0 0 xsh xsh bsh bsh bsh xsh ush 0 7 2 3 1 3 2 flpbsh invbsh invxshu invxshu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 14 20 100 0 1 8 8 0 xxx xxx 0 0 1
Barbed Shield 0 0 3 1 58 78 0 0 65 17 44 0 42 25 0 0 xpk xpk spk spk spk xpk upk 0 7 2 3 1 3 2 flpspk invspk invxpku invxpku 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 18 35 100 0 1 8 8 0 xxx xxx 0 0 1
Expansion
Wolf Head 100 0 1 1 8 11 0 0 16 0 16 0 4 3 0 0 dr1 dr1 dr1 dr1 dr1 dr6 drb 0 0 2 2 1 4 1 flpdr1 invdr1 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Hawk Helm 100 0 1 1 4 15 0 0 20 0 16 0 8 6 0 0 dr2 dr2 dr4 dr4 dr2 dr7 drc 0 0 2 2 1 4 1 flpdr4 invdr2 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Antlers 100 0 1 1 18 24 0 0 24 0 16 0 16 12 0 0 dr3 dr3 dr3 dr3 dr3 dr8 drd 0 0 2 2 1 4 1 flpdr3 invdr3 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Falcon Mask 100 0 1 1 12 28 0 0 28 0 16 0 20 15 0 0 dr4 dr4 dr4 dr4 dr4 dr9 dre 0 0 2 2 1 4 1 flpdr4 invdr4 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Spirit Mask 100 0 1 1 22 35 0 0 30 0 16 0 24 18 0 0 dr5 dr5 dr1 dr1 dr5 dra drf 0 0 2 2 1 4 1 flpdr1 invdr5 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Jawbone Cap 100 0 2 1 10 15 0 0 25 0 20 0 4 3 0 0 ba1 ba1 ba1 dr1 ba1 ba6 bab 0 0 2 2 1 4 1 flpba1 invba1 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 8 0 xxx xxx 0 0 1
Fanged Helm 100 0 2 1 15 20 0 0 35 0 28 0 8 6 0 0 ba2 ba2 ba1 bhm ba2 ba7 bac 0 0 2 2 1 4 1 flpba1 invba2 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 8 0 xxx xxx 0 0 1
Horned Helm 100 0 2 1 25 30 0 0 45 0 36 0 16 12 0 0 ba3 ba3 ba3 bhm ba3 ba8 bad 0 0 2 2 1 4 1 flpba3 invba3 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 8 0 xxx xxx 0 0 1
Assault Helmet 100 0 2 1 30 35 0 0 55 0 40 0 20 15 0 0 ba4 ba4 ba5 bhm ba4 ba9 bae 0 0 2 2 1 4 1 flpba5 invba4 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 8 0 xxx xxx 0 0 1
Avenger Guard 100 0 2 1 35 50 0 0 65 0 44 0 24 18 0 0 ba5 ba5 ba5 bhm ba5 baa baf 0 0 2 2 1 4 1 flpba5 invba5 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 8 0 xxx xxx 0 0 1
Targe 100 0 2 1 8 12 0 0 16 10 16 0 4 3 0 0 pa1 pa1 304 pa1 spk pa1 pa6 pab 0 7 2 2 1 5 2 flppa1 invpa1 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 1 0 0 0 1 0 2 6 100 0 3 0 0 0 xxx xxx 0 0 1
Rondache 100 0 2 1 10 18 0 0 26 15 24 0 8 6 0 0 pa2 pa2 304 pa1 spk pa2 pa7 pac 0 7 2 2 1 5 2 flppa1 invpa2 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 2 0 0 0 1 0 2 8 100 0 3 0 0 0 xxx xxx 0 0 1
Heraldic Shield 100 0 2 1 16 26 0 0 40 20 32 0 16 12 0 0 pa3 pa3 304 pa3 spk pa3 pa8 pad 0 7 2 4 1 5 2 flppa3 invpa3 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 3 0 0 0 1 0 3 9 100 0 3 0 0 0 xxx xxx 0 0 1
Aerin Shield 100 0 2 1 26 36 0 0 50 22 40 0 20 15 0 0 pa4 pa4 304 pa3 spk pa4 pa9 pae 0 7 2 4 1 5 2 flppa3 invpa4 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 4 0 0 0 1 0 4 10 100 0 3 0 0 0 xxx xxx 0 0 1
Crown Shield 100 0 2 1 30 40 0 0 65 25 48 0 24 18 0 0 pa5 pa5 304 pa5 spk pa5 paa paf 0 7 2 2 1 5 2 flppa5 invpa5 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 5 0 0 0 1 0 4 12 100 0 3 0 0 0 xxx xxx 0 0 1
Preserved Head 100 0 1 1 2 5 0 0 12 3 16 0 4 3 0 0 ne1 ne1 305 ne1 dr1 ne1 ne6 neb 0 10 2 2 1 3 2 flpne1 invne1 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Zombie Head 100 0 1 1 4 8 0 0 14 5 16 0 8 6 0 0 ne2 ne2 305 ne2 bhm ne2 ne7 neg 0 10 2 2 1 3 2 flpne2 invne2 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Unraveller Head 100 0 1 1 6 10 0 0 18 8 16 0 16 12 0 0 ne3 ne3 305 ne3 bhm ne3 ne8 ned 0 10 2 2 1 3 2 flpne3 invne3 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Gargoyle Head 100 0 1 1 10 16 0 0 20 10 16 0 20 15 0 0 ne4 ne4 305 ne3 bhm ne4 ne9 nee 0 10 2 2 1 3 2 flpne3 invne4 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Demon Head 100 0 1 1 15 20 0 0 25 12 16 0 24 18 0 0 ne5 ne5 305 ne2 bhm ne5 nea nef 0 10 2 2 1 3 2 flpne2 invne5 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Circlet 100 0 1 1 20 30 0 0 0 0 28 0 24 16 0 0 ci0 ci0 3 lit lit ci0 0 0 2 2 1 3 1 flpci0 invci0 0 0 0 0 0 circ item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Coronet 100 0 1 1 30 40 0 0 0 0 24 0 52 39 0 0 ci1 ci1 8 lit lit ci1 ci2 ci3 0 0 2 2 1 3 1 flpci1 invci1 0 0 0 0 0 circ item_helm 12 item_helm 0 0 5 0 1 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Tiara 100 0 1 1 40 50 0 0 0 0 20 0 70 52 0 0 ci2 ci2 13 lit lit ci1 ci2 ci3 0 0 2 2 1 4 1 flpci1 invci2 0 0 0 0 0 circ item_helm 12 item_helm 0 0 5 0 2 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Diadem 100 0 1 1 50 60 0 0 0 0 16 0 85 64 0 0 ci3 ci3 18 lit lit ci1 ci2 ci3 0 0 2 2 1 4 1 flpci2 invci3 0 0 0 0 0 circ item_helm 12 item_helm 0 0 5 0 3 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Shako 100 0 1 1 98 141 0 0 50 0 10 0 58 43 0 0 uap uap cap cap cap xap uap 0 0 2 2 1 3 1 flpcap invcap 0 0 0 0 0 helm item_cap 12 item_cap 0 0 5 0 0 0 0 0 3 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Hydraskull 100 0 4 1 101 145 0 0 84 0 14 0 63 47 0 0 ukp ukp skp skp skp xkp ukp 0 0 2 2 1 3 1 flpskp invskp 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Armet 100 0 4 1 105 149 0 0 109 0 19 0 68 51 0 0 ulm ulm hlm hlm hlm xlm ulm 0 0 2 2 1 3 1 flphlm invhlm invhlmu invhlmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Giant Conch 100 0 4 1 110 154 0 0 142 0 24 0 54 40 0 0 uhl uhl fhl fhl fhl xhl uhl 0 0 2 2 1 3 1 flpfhl invfhl invfhlu invfhlu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Spired Helm 100 0 4 1 114 159 0 0 192 0 32 0 79 59 0 0 uhm uhm ghm ghm ghm xhm uhm 0 0 2 2 1 4 1 flpghm invghm invuhms 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 7 8 0 xxx xxx 0 0 1
Corona 100 0 4 1 111 165 0 0 174 0 40 0 85 66 0 0 urn urn crn crn crn xrn urn 0 0 2 2 1 4 1 flpcrn invcrn 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Demonhead 100 0 4 1 101 154 0 0 102 0 16 0 74 55 0 0 usk usk msk msk msk xsk usk 0 0 2 2 1 4 1 flpmsk invmsk 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 1 2 0 xxx xxx 0 0 1
Dusk Shroud 100 0 1 1 361 467 0 0 77 0 16 0 65 49 0 0 uui uui qlt qlt qui xui uui 0 1 2 3 1 5 1 flpqlt invqlt 0 0 0 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Wyrmhide 100 0 2 1 364 470 0 0 84 0 19 0 67 50 0 0 uea uea lea lea lea xea uea 0 1 2 3 1 5 1 flplea invlea 0 0 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Scarab Husk 100 0 3 1 369 474 0 0 95 0 22 0 68 51 0 0 ula ula hla hla hla xla ula 0 1 2 3 1 5 1 flphla invhla 1 1 1 0 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Wire Fleece 100 0 4 1 375 481 0 0 111 0 26 0 70 53 0 0 utu utu stu stu stu xtu utu 0 1 2 3 1 5 1 flpstu invstu 1 0 0 1 1 1 0 0 0 0 0 tors item_lightarmor 12 item_lightarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 1 2 8 0 xxx xxx 0 0 1
Diamond Mail 100 0 4 1 383 489 0 5 131 0 21 0 72 54 0 0 ung ung rng rng rng xng ung 0 1 2 3 1 5 1 flprng invrng 0 0 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Loricated Mail 100 0 4 1 390 496 0 10 149 0 29 0 73 55 0 0 ucl ucl scl scl scl xcl ucl 0 1 2 3 1 5 1 flpscl invscl 1 1 1 1 1 1 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Boneweave 100 0 4 1 399 505 0 5 158 0 36 0 62 47 0 0 uhn uhn chn chn chn xhn uhn 0 1 2 3 1 5 1 flpchn invchn 1 1 1 1 2 2 0 0 0 0 0 tors item_chainarmor 12 item_chainarmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Great Hauberk 100 0 4 1 395 501 1 0 118 0 40 0 75 56 0 0 urs urs brs brs brs xrs urs 0 1 2 3 1 5 1 flpbrs invbrs 0 0 2 0 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Balrog Skin 100 0 4 1 410 517 0 5 165 0 24 0 76 57 0 0 upl upl spl spl spl xpl upl 0 1 2 3 1 5 1 flpspl invspl 1 1 2 1 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 2 8 0 xxx xxx 0 0 1
Hellforged Plate 100 0 4 1 421 530 0 10 196 0 48 0 78 59 0 0 ult ult plt plt plt xlt ult 0 1 2 3 1 5 1 flpplt invplt 2 2 2 2 1 1 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Kraken Shell 100 0 4 1 417 523 2 5 174 0 38 0 81 61 0 0 uld uld fld fld fld xld uld 0 1 2 3 1 5 1 flpfld invfld 1 1 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Lacquered Plate 100 0 4 1 433 541 0 5 208 0 44 0 82 62 0 0 uth uth gth gth gth xth uth 0 1 2 3 1 5 1 flpgth invgth 2 2 1 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Shadow Plate 100 0 4 1 446 557 2 10 230 0 56 0 83 64 0 0 uul uul ful ful ful xul uul 0 1 2 3 1 5 1 flpful invful 2 2 2 2 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Sacred Armor 100 0 4 1 487 600 5 5 232 0 48 0 85 66 0 0 uar uar aar aar aar xar uar 0 1 2 3 1 5 1 flpaar invaar invaaru invaaru 1 2 2 2 2 0 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 8 8 0 xxx xxx 0 0 1
Archon Plate 100 0 4 1 410 524 1 0 103 0 48 0 84 63 0 0 utp utp ltp ltp ltp xtp utp 0 1 2 3 1 5 1 flpltp invltp 2 0 1 1 2 2 0 0 0 0 0 tors item_platearmor 12 item_platearmor 0 0 5 0 0 0 0 0 2 0 0 0 0 3 7 8 0 xxx xxx 0 0 1
Heater 100 0 2 1 95 110 0 0 77 22 70 0 58 43 0 0 uuc uuc buc buc buc xuc uuc 0 7 2 2 1 3 2 flpbuc invbuc invbucu invbucu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 16 30 100 0 3 8 8 0 xxx xxx 0 0 1
Luna 100 0 3 1 108 123 0 0 100 20 67 0 61 45 0 0 uml uml buc buc sml xml uml 0 7 2 2 1 3 2 flpsml invsml invsmlu invsmlu 0 0 0 0 0 shie item_woodshield 12 item_woodshield 0 0 5 0 0 0 0 0 1 0 17 29 100 0 3 8 5 0 xxx xxx 0 0 1
Hyperion 100 0 4 1 119 135 0 5 127 24 66 0 64 48 0 0 urg urg lrg lrg lrg xrg urg 0 7 2 3 1 4 2 flplrg invlrg invlrgu invlrgu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 14 32 100 0 3 8 2 0 xxx xxx 0 0 1
Monarch 100 0 4 1 133 148 0 0 156 22 69 0 72 54 0 0 uit uit kit kit kit xit uit 0 7 2 3 1 5 2 flpkit invkit invkitu invkitu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 12 34 100 0 3 8 2 0 xxx xxx 0 0 1
Aegis 100 0 4 1 145 161 1 10 219 24 74 0 79 59 0 0 uow uow tow tow tow xow uow 0 7 2 3 1 5 2 flptow invtow invtowu invtowu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 18 28 100 0 3 8 2 0 xxx xxx 0 0 1
Ward 100 0 4 1 153 170 0 5 185 24 80 0 84 63 0 0 uts uts kit kit gts xts uts 0 7 2 4 1 5 2 flpgts invgts invgtsu invutss 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 11 35 100 0 3 8 2 0 xxx xxx 0 0 1
Bramble Mitts 100 0 1 1 54 62 0 0 50 0 10 0 57 42 0 0 ulg ulg lgl lgl lgl xlg ulg 0 16 2 2 1 4 0 flplgl invlgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Vampirebone Gloves 100 0 1 1 56 65 0 0 50 0 11 0 63 47 0 0 uvg uvg vgl vgl vgl xvg uvg 0 16 2 2 1 4 0 flpvgl invvgl 0 0 0 0 0 glov item_gloves 12 item_gloves 0 0 5 0 0 0 0 0 5 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Vambraces 100 0 2 1 59 67 0 0 106 0 13 0 69 51 0 0 umg umg mgl mgl mgl xmg umg 0 16 2 2 1 4 0 flpmgl invmgl 0 0 0 0 0 glov item_gloveschain 12 item_gloveschain 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Crusader Gauntlets 100 0 3 1 59 68 0 0 151 0 14 0 76 57 0 0 utg utg mgl mgl tgl xtg utg 0 16 2 2 1 4 0 flptgl invtgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Ogre Gauntlets 100 0 4 1 62 71 0 0 185 0 19 0 85 64 0 0 uhg uhg hgl hgl hgl xhg uhg 0 16 2 2 1 4 0 flphgl invhgl 0 0 0 0 0 glov item_glovesmetal 12 item_glovesmetal 0 0 5 0 0 0 0 0 5 0 0 0 0 3 0 8 0 xxx xxx 0 0 1
Wyrmhide Boots 100 0 1 1 54 62 0 0 50 0 10 0 60 45 0 0 ulb ulb lbt lbt lbt xlb ulb 0 16 2 2 1 4 1 flplbt invlbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 65 100 120 0 1 0 8 0 xxx xxx 0 0 1
Scarabshell Boots 100 0 1 1 56 65 0 0 91 0 11 0 66 49 0 0 uvb uvb vbt vbt vbt xvb uvb 0 16 2 2 1 4 1 flpvbt invvbt 0 0 0 0 0 boot item_boots 12 item_boots 0 0 5 0 0 0 0 0 6 0 60 110 120 0 1 0 8 0 xxx xxx 0 0 1
Boneweave Boots 100 0 2 1 59 67 0 0 118 0 13 0 72 54 0 0 umb umb mbt mbt mbt xmb umb 0 16 2 2 1 4 1 flpmbt invmbt 0 0 0 0 0 boot item_bootschain 12 item_bootschain 0 0 5 0 0 0 0 0 6 0 69 118 120 0 3 0 8 0 xxx xxx 0 0 1
Mirrored Boots 100 0 3 1 59 68 0 0 163 0 14 0 81 60 0 0 utb utb mbt mbt tbt xtb utb 0 16 2 2 1 4 1 flptbt invtbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 50 145 120 0 3 0 8 0 xxx xxx 0 0 1
Myrmidon Greaves 100 0 4 1 62 71 0 0 208 0 19 0 85 65 0 0 uhb uhb hbt hbt hbt xhb uhb 0 16 2 2 1 4 1 flphbt invhbt 0 0 0 0 0 boot item_bootsmetal 12 item_bootsmetal 0 0 5 0 0 0 0 0 6 0 83 149 120 0 3 0 8 0 xxx xxx 0 0 1
Spiderweb Sash 100 0 1 1 55 62 0 0 50 0 10 0 61 46 0 0 ulc ulc lbl lbl lbl zlb ulc 0 16 2 1 1 1 0 flplbl invlbl 0 0 0 0 0 belt item_lightarmor 12 item_lightarmor 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Vampirefang Belt 100 0 1 1 56 63 0 0 50 0 11 0 68 51 0 0 uvc uvc vbl vbl vbl zvb uvc 0 16 2 1 1 1 0 flpvbl invvbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Mithril Coil 100 0 2 1 58 65 0 0 106 0 13 0 75 56 0 0 umc umc mbl mbl mbl zmb umc 0 16 2 1 1 1 0 flpmbl invmbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Troll Belt 100 0 2 1 59 66 0 0 151 0 14 0 82 62 0 0 utc utc mbl mbl tbl ztb utc 0 16 2 1 1 1 0 flptbl invtbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Colossus Girdle 100 0 3 1 61 71 0 0 185 0 19 0 85 67 0 0 uhc uhc hbl hbl hbl zhb uhc 0 16 2 1 1 1 0 flphbl invhbl 0 0 0 0 0 belt item_belt 12 item_belt 0 0 5 0 0 6 0 0 4 0 0 0 0 1 0 8 0 xxx xxx 0 0 1
Bone Visage 100 0 2 1 100 157 0 0 106 0 32 0 84 63 0 0 uh9 uh9 bhm bhm bhm xh9 uh9 0 0 2 2 1 4 1 flpbhm invbhm invbhmu invbhmu 0 0 0 0 0 helm item_helm 12 item_helm 0 0 5 0 2 0 0 0 3 0 0 0 0 1 7 8 0 xxx xxx 0 0 1
Troll Nest 100 0 2 1 158 173 0 0 106 20 59 0 76 57 0 0 ush ush bsh bsh bsh xsh ush 0 7 2 3 1 4 2 flpbsh invbsh invbshu invbshu 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 24 38 100 0 1 8 8 0 xxx xxx 0 0 1
Blade Barrier 100 0 3 1 147 163 0 0 118 20 66 0 68 51 0 0 upk upk spk spk spk xpk upk 0 7 2 3 1 4 2 flpspk invspk invspku invspku 0 0 0 0 0 shie item_metalshield 12 item_metalshield 0 0 5 0 0 0 0 0 1 0 26 40 100 0 1 8 8 0 xxx xxx 0 0 1
Alpha Helm 100 0 1 1 52 62 0 0 44 0 16 0 35 26 0 0 dr6 dr6 dr1 dr1 dr1 dr6 drb 0 0 2 2 1 4 1 flpdr1 invdr1 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Griffon Headress 100 0 1 1 46 68 0 0 50 0 16 0 40 30 0 0 dr7 dr7 dr4 dr4 dr2 dr7 drc 0 0 2 2 1 4 1 flpdr4 invdr2 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Hunter's Guise 100 0 1 1 67 81 0 0 56 0 16 0 46 29 0 0 dr8 dr8 dr3 dr3 dr3 dr8 drd 0 0 2 2 1 4 1 flpdr3 invdr3 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Sacred Feathers 100 0 1 1 58 87 0 0 62 0 16 0 50 32 0 0 dr9 dr9 dr4 dr4 dr4 dr9 dre 0 0 2 2 1 4 1 flpdr4 invdr4 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Totemic Mask 100 0 1 1 73 98 0 0 65 0 16 0 55 41 0 0 dra dra dr1 dr1 dr5 dra drf 0 0 2 2 1 4 1 flpdr1 invdr5 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Jawbone Visor 100 0 2 1 55 68 0 0 58 0 20 0 33 25 0 0 ba6 ba6 ba1 dr1 ba1 ba6 bab 0 0 2 2 1 4 1 flpba1 invba1 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 0 0 xxx xxx 0 0 1
Lion Helm 100 0 2 1 63 75 0 0 73 0 28 0 38 29 0 0 ba7 ba7 ba1 bhm ba2 ba7 bac 0 0 2 2 1 4 1 flpba1 invba2 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 0 0 xxx xxx 0 0 1
Rage Mask 100 0 2 1 78 90 0 0 88 0 36 0 44 29 0 0 ba8 ba8 ba3 bhm ba3 ba8 bad 0 0 2 2 1 4 1 flpba3 invba3 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Savage Helmet 100 0 2 1 85 98 0 0 103 0 40 0 49 32 0 0 ba9 ba9 ba5 bhm ba4 ba9 bae 0 0 2 2 1 4 1 flpba5 invba4 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Slayer Guard 100 0 2 1 93 120 0 0 118 0 44 0 54 40 0 0 baa baa ba5 bhm ba5 baa baf 0 0 2 2 1 4 1 flpba5 invba5 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Akaran Targe 100 0 2 1 101 125 0 0 44 10 16 0 35 26 0 0 pa6 pa6 304 pa1 spk pa1 pa6 pab 0 7 2 2 1 5 2 flppa1 invpa1 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 1 0 0 0 1 0 12 16 100 0 3 0 0 0 xxx xxx 0 0 1
Akaran Rondache 100 0 2 1 113 137 0 0 59 15 24 0 40 30 0 0 pa7 pa7 304 pa1 spk pa2 pa7 pac 0 7 2 2 1 5 2 flppa1 invpa2 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 2 0 0 0 1 0 15 20 100 0 3 0 0 0 xxx xxx 0 0 1
Protector Shield 100 0 2 1 129 153 0 0 69 20 32 0 46 34 0 0 pa8 pa8 304 pa3 spk pa3 pa8 pad 0 7 2 4 1 5 2 flppa3 invpa3 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 3 0 0 0 1 0 18 24 100 0 3 0 0 0 xxx xxx 0 0 1
Guilded Shield 100 0 2 1 144 168 0 0 89 22 40 0 51 38 0 0 pa9 pa9 304 pa3 spk pa4 pa9 pae 0 7 2 4 1 5 2 flppa3 invpa4 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 4 0 0 0 1 0 20 28 100 0 3 0 0 0 xxx xxx 0 0 1
Royal Shield 100 0 2 1 156 181 0 0 114 25 48 0 55 41 0 0 paa paa 304 pa5 spk pa5 paa paf 0 7 2 2 1 5 2 flppa5 invpa5 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 5 0 0 0 1 0 24 32 100 0 3 0 0 0 xxx xxx 0 0 1
Mummified Trophy 100 0 1 1 38 48 0 0 38 3 16 0 33 24 0 0 ne6 ne6 305 ne1 dr1 ne1 ne6 neb 0 10 2 2 1 3 2 flpne1 invne1 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Fetish Trophy 100 0 1 1 41 52 0 0 41 5 16 0 39 29 0 0 ne7 ne7 305 ne2 bhm ne2 ne7 neg 0 10 2 2 1 3 2 flpne2 invne2 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Sexton Trophy 100 0 1 1 44 55 0 0 47 8 16 0 45 33 0 0 ne8 ne8 305 ne3 bhm ne3 ne8 ned 0 10 2 2 1 3 2 flpne3 invne3 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Cantor Trophy 100 0 1 1 50 64 0 0 50 10 16 0 49 36 0 0 ne9 ne9 305 ne3 bhm ne4 ne9 nee 0 10 2 2 1 3 2 flpne3 invne4 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Heirophant Trophy 100 0 1 1 58 70 0 0 58 12 16 0 54 40 0 0 nea nea 305 ne2 bhm ne5 nea nef 0 10 2 2 1 3 2 flpne2 invne5 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Blood Spirt 100 0 1 1 101 145 0 0 86 0 16 0 62 46 0 0 drb drb dr1 dr1 dr1 dr6 drb 0 0 2 2 1 4 1 flpdr1 invdr1 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Sun Spirit 100 0 1 1 98 147 0 0 95 0 16 0 69 51 0 0 drc drc dr4 dr4 dr2 dr7 drc 0 0 2 2 1 4 1 flpdr4 invdr2 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Earth Spirit 100 0 1 1 107 152 0 0 104 0 16 0 76 57 0 0 drd drd dr3 dr3 dr3 dr8 drd 0 0 2 2 1 4 1 flpdr3 invdr3 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Sky Spirit 100 0 1 1 103 155 0 0 113 0 16 0 83 62 0 0 dre dre dr4 dr4 dr4 dr9 dre 0 0 2 2 1 4 1 flpdr4 invdr4 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Dream Spirit 100 0 1 1 109 159 0 0 118 0 16 0 85 66 0 0 drf drf dr1 dr1 dr5 dra drf 0 0 2 2 1 4 1 flpdr1 invdr5 0 0 0 0 0 pelt item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 8 8 0 xxx xxx 0 0 1
Carnage Helm 100 0 2 1 102 147 0 0 106 0 20 0 60 45 0 0 bab bab ba1 dr1 ba1 ba6 bab 0 0 2 2 1 4 1 flpba1 invba1 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 0 0 xxx xxx 0 0 1
Fury Visor 100 0 2 1 105 150 0 0 129 0 28 0 66 49 0 0 bac bac ba1 bhm ba2 ba7 bac 0 0 2 2 1 4 1 flpba1 invba2 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 1 5 0 0 xxx xxx 0 0 1
Destroyer Helm 100 0 2 1 111 156 0 0 151 0 36 0 73 54 0 0 bad bad ba3 bhm ba3 ba8 bad 0 0 2 2 1 4 1 flpba3 invba3 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Conquerer Crown 100 0 2 1 114 159 0 0 174 0 40 0 80 60 0 0 bae bae ba5 bhm ba4 ba9 bae 0 0 2 2 1 4 1 flpba5 invba4 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Guardian Crown 100 0 2 1 117 168 0 0 196 0 44 0 85 65 0 0 baf baf ba5 bhm ba5 baa baf 0 0 2 2 1 4 1 flpba5 invba5 0 0 0 0 0 phlm item_helm 12 item_helm 0 0 5 0 0 0 0 0 3 0 0 0 0 3 5 0 0 xxx xxx 0 0 1
Sacred Targe 100 0 2 1 126 158 0 0 86 30 36 0 63 47 0 0 pab pab 304 pa1 spk pa1 pa6 pab 0 7 2 2 1 5 2 flppa1 invpa1 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 1 0 0 0 1 0 22 70 100 0 3 0 0 0 xxx xxx 0 0 1
Sacred Rondache 100 0 2 1 138 164 0 0 109 28 54 0 70 52 0 0 pac pac 304 pa1 spk pa2 pa7 pac 0 7 2 2 1 5 2 flppa1 invpa2 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 2 0 0 0 1 0 35 58 100 0 3 0 0 0 xxx xxx 0 0 1
Ancient Shield 100 0 2 1 154 172 0 0 124 25 44 0 74 55 0 0 pad pad 304 pa3 spk pa3 pa8 pad 0 7 2 4 1 5 2 flppa3 invpa3 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 3 0 0 0 1 0 10 82 100 0 3 0 0 0 xxx xxx 0 0 1
Zakarum Shield 100 0 2 1 169 193 0 0 142 22 52 0 82 61 0 0 pae pae 304 pa3 spk pa4 pa9 pae 0 7 2 4 1 5 2 flppa3 invpa4 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 4 0 0 0 1 0 46 46 100 0 3 0 0 0 xxx xxx 0 0 1
Vortex Shield 100 0 2 1 182 225 0 0 148 19 72 0 85 66 0 0 paf paf 304 pa5 spk pa5 paa paf 0 7 2 2 1 5 2 flppa5 invpa5 0 0 0 0 0 ashd item_metalshield 12 item_metalshield 0 0 5 0 5 0 0 0 1 0 5 87 100 0 3 0 0 0 xxx xxx 0 0 1
Minion Skull 100 0 1 1 95 139 0 0 77 3 16 0 59 44 0 0 neb neb 305 ne1 dr1 ne1 ne6 neb 0 10 2 2 1 3 2 flpne1 invne1 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Hellspawn Skull 100 0 1 1 96 141 0 0 82 5 16 0 67 50 0 0 neg neg 305 ne2 bhm ne2 ne7 neg 0 10 2 2 1 3 2 flpne2 invne2 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Overseer Skull 100 0 1 1 98 142 0 0 91 8 16 0 66 49 0 0 ned ned 305 ne3 bhm ne3 ne8 ned 0 10 2 2 1 3 2 flpne3 invne3 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Succubae Skull 100 0 1 1 100 146 0 0 95 10 16 0 81 60 0 0 nee nee 305 ne3 bhm ne4 ne9 nee 0 10 2 2 1 3 2 flpne3 invne4 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1
Bloodlord Skull 100 0 1 1 103 148 0 0 106 12 16 0 85 65 0 0 nef nef 305 ne2 bhm ne5 nea nef 0 10 2 2 1 3 2 flpne2 invne5 0 0 0 0 0 head item_head 12 item_head 0 0 5 0 0 0 0 0 3 0 0 0 0 1 1 0 0 xxx xxx 0 0 1

429
Properties.txt Normal file
View File

@ -0,0 +1,429 @@
code *done set1 val1 func1 stat1 set2 val2 func2 stat2 set3 val3 func3 stat3 set4 val4 func4 stat4 set5 val5 func5 stat5 set6 val6 func6 stat6 set7 val7 func7 stat7 *desc *param *min *max *notes *eol
ac 1 1 armorclass none 0
ac-miss 1 1 armorclass_vs_missile none 0
ac-hth 1 1 armorclass_vs_hth none 0
red-dmg 1 1 normal_damage_reduction none 0
red-dmg% 1 1 damageresist none 0
ac% 1 2 item_armor_percent none 0
red-mag 1 1 magic_damage_reduction none 0
str 1 1 strength none 0
dex 1 1 dexterity none 0
vit 1 1 vitality none 0
enr 1 1 energy none 0
mana 1 1 maxmana none 0
mana% 1 1 item_maxmana_percent none 0
hp 1 1 maxhp none 0
hp% 1 1 item_maxhp_percent none 0
att 1 1 tohit none 0
block 1 1 toblock none 0
cold-min 1 1 coldmindam none 0
cold-max 1 1 coldmaxdam none 0
cold-len 1 1 coldlength none 0
fire-min 1 1 firemindam none 0
fire-max 1 1 firemaxdam none 0
ltng-min 1 1 lightmindam none 0
ltng-max 1 1 lightmaxdam none 0
pois-min 1 1 poisonmindam none 0
pois-max 1 1 poisonmaxdam none 0
pois-len 1 1 poisonlength none 0
dmg-min 1 5 none 0
dmg-max 1 6 none 0
dmg% 1 7 none 0
dmg-to-mana 1 1 item_damagetomana none 0
res-fire 1 1 fireresist none 0
res-fire-max 1 1 maxfireresist none 0
res-ltng 1 1 lightresist none 0
res-ltng-max 1 1 maxlightresist none 0
res-cold 1 1 coldresist none 0
res-cold-max 1 1 maxcoldresist none 0
res-mag 1 1 magicresist none 0
res-mag-max 1 1 maxmagicresist none 0
res-pois 1 1 poisonresist none 0
res-pois-max 1 1 maxpoisonresist none 0
res-all 1 1 fireresist 3 lightresist 3 coldresist 3 poisonresist none 0
res-all-max 1 1 maxfireresist 3 maxlightresist 3 maxcoldresist 3 maxpoisonresist none 0
abs-fire% 1 1 item_absorbfire_percent none 0
abs-fire 1 1 item_absorbfire none 0
abs-ltng% 1 1 item_absorblight_percent none 0
abs-ltng 1 1 item_absorblight none 0
abs-mag% 1 1 item_absorbmagic_percent none 0
abs-mag 1 1 item_absorbmagic none 0
abs-cold% 1 1 item_absorbcold_percent none 0
abs-cold 1 1 item_absorbcold none 0
dur 1 1 durability none 0
dur% 1 13 item_maxdurability_percent none 0
regen 1 1 hpregen none 0
thorns 1 1 item_attackertakesdamage none 0
swing1 1 8 item_fasterattackrate none 0
swing2 1 8 item_fasterattackrate none 0
swing3 1 8 item_fasterattackrate none 0
gold% 1 1 item_goldbonus none 0
mag% 1 1 item_magicbonus none 0
knock 1 1 item_knockback none 0
regen-stam 1 1 staminarecoverybonus none 0
regen-mana 1 1 manarecoverybonus none 0
stam 1 1 maxstamina none 0
time 1 1 item_timeduration none 0
manasteal 1 1 manadrainmindam none 0
lifesteal 1 1 lifedrainmindam none 0
ama 1 0 21 item_addclassskills none 0
pal 1 3 21 item_addclassskills none 0
nec 1 2 21 item_addclassskills none 0
sor 1 1 21 item_addclassskills none 0
bar 1 4 21 item_addclassskills none 0
herb 1 1 item_doubleherbduration none 0
light 1 1 item_lightradius none 0
color 1 1 1 item_lightcolor none 0
ease 1 1 item_req_percent none 0
move1 1 8 item_fastermovevelocity none 0
move2 1 8 item_fastermovevelocity none 0
move3 1 8 item_fastermovevelocity none 0
balance1 1 8 item_fastergethitrate none 0
balance2 1 8 item_fastergethitrate none 0
balance3 1 8 item_fastergethitrate none 0
block1 1 8 item_fasterblockrate none 0
block2 1 8 item_fasterblockrate none 0
block3 1 8 item_fasterblockrate none 0
cast1 1 8 item_fastercastrate none 0
cast2 1 8 item_fastercastrate none 0
cast3 1 8 item_fastercastrate none 0
res-pois-len 1 1 item_poisonlengthresist none 0
dmg 1 1 item_normaldamage none 0
howl 1 1 item_howl none 0
stupidity 1 1 item_stupidity none 0
ignore-ac 1 1 item_ignoretargetac none 0
reduce-ac 1 1 item_fractionaltargetac none 0
noheal 1 1 item_preventheal none 0
half-freeze 1 1 item_halffreezeduration none 0
att% 1 1 item_tohit_percent none 0
dmg-ac 1 1 item_damagetargetac none 0
dmg-demon 1 1 item_demondamage_percent none 0
dmg-undead 1 1 item_undeaddamage_percent none 0
att-demon 1 1 item_demon_tohit none 0
att-undead 1 1 item_undead_tohit none 0
throw 1 1 item_throwable none 0
fireskill 1 1 21 item_elemskill 1 item_elemskillfire none 0
allskills 1 1 item_allskills none 0
light-thorns 1 1 item_attackertakeslightdamage none 0
freeze 1 1 item_freeze chance in 128 length in frames none 0
openwounds 1 1 item_openwounds none 0
crush 1 1 item_crushingblow none 0
kick 1 1 item_kickdamage none 0
mana-kill 1 1 item_manaafterkill none 0
demon-heal 1 1 item_healafterdemonkill none 0
bloody 1 1 item_extrablood none 0
deadly 1 1 item_deadlystrike none 0
slow 1 1 item_slow none 0
nofreeze 1 1 item_cannotbefrozen none 0
stamdrain 1 1 item_staminadrainpct none 0
reanimate 1 24 item_reanimate none 0
pierce 1 1 item_pierce none 0
magicarrow 1 1 item_magicarrow none 0
explosivearrow 1 1 item_explosivearrow none 0
Expansion 0
dru 1 5 21 item_addclassskills All Druid Skills none 0
ass 1 6 21 item_addclassskills All Assassin Skills none 0
skill 1 22 item_singleskill none 0
skilltab 1 10 item_addskill_tab none 0
aura 1 22 item_aura none 0
att-skill 1 11 item_skillonattack Proc Skill on Swing Skill # % Chance Level none 0
hit-skill 1 11 item_skillonhit Proc Skill on Hit Skill # % Chance Level none 0
gethit-skill 1 11 item_skillongethit Proc Skill on Get Hit Skill # % Chance Level none 0
gembonus Increase chance of finding Gems none 0
regen-dur none 0
fire-fx none 0
ltng-fx none 0
sock 1 14 item_numsockets none 0
dmg-fire 1 15 firemindam 16 firemaxdam Fire Damage Min Max none 0
dmg-ltng 1 15 lightmindam 16 lightmaxdam Lightning Damage Min Max none 0
dmg-mag 1 15 magicmindam 16 magicmaxdam Magic Damge Min Max none 0
dmg-cold 1 15 coldmindam 16 coldmaxdam 17 coldlength Cold Damage Length (Frames) Min Max none 0
dmg-pois 1 15 poisonmindam 16 poisonmaxdam 17 poisonlength Poison Damage Length (Frames) Min Max none 0
dmg-throw 1 15 item_throw_mindamage 16 item_throw_maxdamage Throwing Damage Min Max none 0
dmg-norm 1 15 mindamage 16 maxdamage Normal Damage Modifier Min Max none 0
ac/lvl 1 17 item_armor_perlevel AC per Player Level ac/lvl (8ths) none 0
ac%/lvl 1 17 item_armorpercent_perlevel AC% per Player Level ac%/lvl (8ths) none 0
hp/lvl 1 17 item_hp_perlevel HP per Player Level hp/lvl (8ths) none 0
mana/lvl 1 17 item_mana_perlevel Mana per Player Level (8ths) none 0
dmg/lvl 1 17 item_maxdamage_perlevel Max Damage per Player Level (8ths) none 0
dmg%/lvl 1 17 item_maxdamage_percent_perlevel Max Damage % per Player Level (8ths) none 0
str/lvl 1 17 item_strength_perlevel Strength per Player Level (8ths) none 0
dex/lvl 1 17 item_dexterity_perlevel Dexterity per Player Level (8ths) none 0
enr/lvl 1 17 item_energy_perlevel Energy per Player Level (8ths) none 0
vit/lvl 1 17 item_vitality_perlevel Vitality per Player Level (8ths) none 0
att/lvl 1 17 item_tohit_perlevel Attack per Player Level att/lvl (1) none 0
att%/lvl 1 17 item_tohitpercent_perlevel Attack% per Player Level att%/lvl (8ths) none 0
dmg-cold/lvl 1 17 item_cold_damagemax_perlevel Max Cold Damage per Player Level (8ths) none 0
dmg-fire/lvl 1 17 item_fire_damagemax_perlevel Max Fire Damage per Player Level (8ths) none 0
dmg-ltng/lvl 1 17 item_ltng_damagemax_perlevel Max Lightning Dmg per Player Level (8ths) none 0
dmg-pois/lvl 1 17 item_pois_damagemax_perlevel Max Poison Dmg per Player Level (8ths) none 0
res-cold/lvl 1 17 item_resist_cold_perlevel Resist Cold% per Player Level (8ths) none 0
res-fire/lvl 1 17 item_resist_fire_perlevel Resist Fire% per Player Level (8ths) none 0
res-ltng/lvl 1 17 item_resist_ltng_perlevel Resist Lightning% per Player Level (8ths) none 0
res-pois/lvl 1 17 item_resist_pois_perlevel Resist Poison% per Player Level (8ths) none 0
abs-cold/lvl 1 17 item_absorb_cold_perlevel Absorb Cold Dmg per Player Level (8ths) none 0
abs-fire/lvl 1 17 item_absorb_fire_perlevel Absorb Fire Dmg per Player Level (8ths) none 0
abs-ltng/lvl 1 17 item_absorb_ltng_perlevel Absorb Lightning Dmg per Player Lvl (8ths) none 0
abs-pois/lvl 1 17 item_absorb_pois_perlevel Absorb Poison Dmg per Player Lvl (8ths) none 0
thorns/lvl 1 17 item_thorns_perlevel Damage to Attacker per Player Lvl (8ths) none 0
gold%/lvl 1 17 item_find_gold_perlevel +% Gold Dropped per Player Lvl (8ths) none 0
mag%/lvl 1 17 item_find_magic_perlevel +% Magical per Player Lvl (8ths) none 0
regen-stam/lvl 1 17 item_regenstamina_perlevel +% Stamina Regeneration per Player Lvl (8ths) none 0
stam/lvl 1 17 item_stamina_perlevel Stamina per Player Level (8ths) none 0
dmg-dem/lvl 1 17 item_damage_demon_perlevel Damage to Demons % per Player Level (8ths) none 0
dmg-und/lvl 1 17 item_damage_undead_perlevel Damage to Undead % per Player Level (8ths) none 0
att-dem/lvl 1 17 item_tohit_demon_perlevel Attack Demons + per Player Level att/lvl (1) none 0
att-und/lvl 1 17 item_tohit_undead_perlevel Attack Undead + per Player Level att/lvl (1) none 0
crush/lvl 1 17 item_crushingblow_perlevel +% Chance of Crushing Blow per Player Level (8ths) none 0
wounds/lvl 1 17 item_openwounds_perlevel +% Chance of Open Wounds per Player Level (8ths) none 0
kick/lvl 1 17 item_kick_damage_perlevel Kick Damage per Player Level (8ths) none 0
deadly/lvl 1 17 item_deadlystrike_perlevel +% Chance of Deadly Strike per Player Level (8ths) none 0
gems%/lvl +% Chance of finding Gems per Player Level (8ths) none 0
rep-dur 1 17 item_replenish_durability regenerates durability speed (see note) regenerates 1 durability point per 100/lvl seconds 0
rep-quant 1 17 item_replenish_quantity regenerates quantity speed (see note) regenerates 1 quantity per 100/lvl seconds 0
stack 1 1 item_extra_stack Increased stack size min count max count none 0
item% +% Chance of finding item item type min chance max chance random chance from min to max is assigned to item 0
dmg-slash Slashing Damage min max random amount from min to max is assigned to item 0
dmg-slash% Slashing Damage % min % max % random amount from min to max is assigned to item 0
dmg-crush Crush Damage min max random amount from min to max is assigned to item 0
dmg-crush% Crush Damage % min % max % random amount from min to max is assigned to item 0
dmg-thrust Thrust Damage min max random amount from min to max is assigned to item 0
dmg-thrust% Thrust Damage % min % max % random amount from min to max is assigned to item 0
abs-slash Absorb Slashing Damage min amt max amt random amount from min to max is assigned to item 0
abs-crush Absorb Crushing Damage min amt max amt random amount from min to max is assigned to item 0
abs-thrust Absorb Thrusting Damage min amt max amt random amount from min to max is assigned to item 0
abs-slash% Absorb Slashing Damage % min % max % random amount from min to max is assigned to item 0
abs-crush% Absorb Crushing Damage % min % max % random amount from min to max is assigned to item 0
abs-thrust% Absorb Thrusting Damage % min % max % random amount from min to max is assigned to item 0
ac/time 1 18 item_armor_bytime "AC / time increment (0=day, 1=dusk, 2=night, 3=dawn)" center period min max "max at center period, min at opposite period, linear progression" 0
ac%/time 1 18 item_armorpercent_bytime AC% / time increment (8 periods) center period min max "max at center period, min at opposite period, linear progression" 0
hp/time 1 18 item_hp_bytime HP / time increment center period min max "max at center period, min at opposite period, linear progression" 0
mana/time 1 18 item_mana_bytime Mana / time increment center period min max "max at center period, min at opposite period, linear progression" 0
dmg/time 1 18 item_maxdamage_bytime Max Damage / time increment center period min max "max at center period, min at opposite period, linear progression" 0
dmg%/time 1 18 item_maxdamage_percent_bytime Max Damage % / time increment center period min max "max at center period, min at opposite period, linear progression" 0
str/time 1 18 item_strength_bytime Strength / time increment center period min max "max at center period, min at opposite period, linear progression" 0
dex/time 1 18 item_dexterity_bytime Dexterity / time increment center period min max "max at center period, min at opposite period, linear progression" 0
enr/time 1 18 item_energy_bytime Energy / time increment center period min max "max at center period, min at opposite period, linear progression" 0
vit/time 1 18 item_vitality_bytime Vitality / time increment center period min max "max at center period, min at opposite period, linear progression" 0
att/time 1 18 item_tohit_bytime To hit / time increment center period min max "max at center period, min at opposite period, linear progression" 0
att%/time 1 18 item_tohitpercent_bytime To Hit % / time increment center period min max "max at center period, min at opposite period, linear progression" 0
dmg-cold/time 1 18 item_cold_damagemax_bytime Cold Damage Max / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
dmg-fire/time 1 18 item_fire_damagemax_bytime Fire Damage Max / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
dmg-ltng/time 1 18 item_ltng_damagemax_bytime Lightning Damage Max / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
dmg-pois/time 1 18 item_pois_damagemax_bytime Poison Damage Max / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
res-cold/time 1 18 item_resist_cold_bytime Resist Cold / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
res-fire/time 1 18 item_resist_fire_bytime Resist Fire / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
res-ltng/time 1 18 item_resist_ltng_bytime Resist Lightning / time inc center period min max "max at center period, min at opposite period, linear progression" 0
res-pois/time 1 18 item_resist_pois_bytime Resist Poison / time inc center period min max "max at center period, min at opposite period, linear progression" 0
abs-cold/time 1 18 item_absorb_cold_bytime Absorb Cold / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
abs-fire/time 1 18 item_absorb_fire_bytime Absorb Fire / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
abs-ltng/time 1 18 item_absorb_ltng_bytime Absorb Lightning / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
abs-pois/time 1 18 item_absorb_pois_bytime Absorb Poison / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
gold%/time 1 18 item_find_gold_bytime Find Gold Amt % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
mag%/time 1 18 item_find_magic_bytime Find Magic % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
regen-stam/time 1 18 item_regenstamina_bytime % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
stam/time 1 18 item_stamina_bytime Stamina / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
dmg-dem/time 1 18 item_damage_demon_bytime Damage to Demons % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
dmg-und/time 1 18 item_damage_undead_bytime Damage to Undead % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
att-dem/time 1 18 item_tohit_demon_bytime To Hit Demons % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
att-und/time 1 18 item_tohit_undead_bytime To Hit Undead % / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
crush/time 1 18 item_crushingblow_bytime % chance of Crushing Blow / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
wounds/time 1 18 item_openwounds_bytime +% chance of Open Wounds / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
kick/time 1 18 item_kick_damage_bytime Kick Damage / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
deadly/time 1 18 item_deadlystrike_bytime +% chance of Deadly Strike / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
gems%/time +% chance of finding Gems / time inc. center period min max "max at center period, min at opposite period, linear progression" 0
pierce-fire 1 1 passive_fire_pierce Negates % of Enemy Cold Resistance min max none 0
pierce-ltng 1 1 passive_ltng_pierce Negates % of Enemy Fire Resistance min max none 0
pierce-cold 1 1 passive_cold_pierce Negates % of Enemy Lightning Resistance min max none 0
pierce-pois 1 1 passive_pois_pierce Negates % of Enemy Poison Resistance min max none 0
dmg-mon Damage vs. specific Monster Type monster min max none 0
dmg%-mon Damage % vs. specific Monster Type monster min max none 0
att-mon To Hit vs. specific Monster Type monster min max none 0
att%-mon To Hit % vs. specific Monster Type monster min max none 0
ac-mon AC vs. specific Monster Type monster min max none 0
ac%-mon AC% vs. specific Monster Type monster min max none 0
indestruct 1 20 Indestructible none 0
charged 1 19 item_charged_skill Charged Skill skill charges level none 0
extra-fire 1 1 passive_fire_mastery 0
extra-ltng 1 1 passive_ltng_mastery 0
extra-cold 1 1 passive_cold_mastery 0
extra-pois 1 1 passive_pois_mastery 0
dmg-elem 1 15 firemindam 16 firemaxdam 15 lightmindam 16 lightmaxdam 15 coldmindam 16 coldmaxdam 17 coldlength 0
dmg-elem-min 1 1 firemindam 3 lightmindam 3 coldmindam 0
dmg-elem-max 1 1 firemaxdam 3 lightmaxdam 3 coldmaxdam 17 coldlength 0
all-stats 1 1 strength 3 energy 3 dexterity 3 vitality 0
addxp 1 1 item_addexperience additional xp gain 0
heal-kill 1 1 item_healafterkill life gained after each kill 0
cheap 1 1 item_reducedprices reduces vendor prices min max none 0
rip 1 1 item_restinpeace slain monsters' corpses can't be used 1 1 none 0
att-mon% 1 24 attack_vs_montype attack% vs. monster type mon type min max 0
dmg-mon% 1 24 damage_vs_montype damage% vs. monster type mon type min max 0
kill-skill 1 11 item_skillonkill Proc Skill on killing something Skill # % Chance Level 0
death-skill 1 11 item_skillondeath Proc Skill on getting killed Skill # % Chance Level 0
levelup-skill 1 11 item_skillonlevelup Proc Skill on level up Skill # % Chance Level 0
skill-rand 1 12 item_singleskill Bonus to random skill Level min Skill # max Skill # 0
fade 1 17 fade only use on monprop fade type 0
levelreq 1 1 item_levelreq 0
ethereal 1 23 0
oskill 1 22 item_nonclassskill Skill min level max level none 0
state 1 24 state state 1 1 0
str% 1 1 item_strength_percent 0
dex% 1 1 item_dexterity_percent 0
vit% 1 1 item_vitality_percent 0
enr% 1 1 item_energy_percent 0
str%/lvl 1 17 item_strengthpercent_perlevel Strength per Player Level (8ths) none 0
dex%/lvl 1 17 item_dexteritypercent_perlevel Dexterity per Player Level (8ths) none 0
enr%/lvl 1 17 item_energypercent_perlevel Energy per Player Level (8ths) none 0
vit%/lvl 1 17 item_vitalitypercent_perlevel Vitality per Player Level (8ths) none 0
thorns-blind 1 11 item_attackergetsblind attacker gets blinded min max armour only 0
thorns-flee 1 11 item_attackerflees attacker runs away min max armour only 0
fire-thorns 1 1 item_attackertakesfiredamage 0
cold-thorns 1 1 item_attackertakescolddamage 0
dmg-max/str 1 17 item_maxdamage_perstr (8ths) 0
dmg-max/dex 1 17 item_maxdamage_perdex (8ths) 0
dmg-min/lvl 1 17 item_mindamage_perlvl (8ths) 0
dmg-min/str 1 17 item_mindamage_perstr (8ths) 0
dmg-min/dex 1 17 item_mindamage_perdex (8ths) 0
dmg%/str 1 17 item_maxdamage_percent_perstr (8ths) 0
dmg%/dex 1 17 item_maxdamage_percent_perdex (8ths) 0
wounds/dex 1 17 item_openwounds_perdex (8ths) 0
deadly/dex 1 17 item_deadlystrike_perdex (8ths) 0
wounds/str 1 17 item_openwounds_perstr (8ths) 0
deadly/str 1 17 item_deadlystrike_perstr (8ths) 0
ac/str 1 17 item_armor_perstr armour per strength (8ths) armour only 0
ac/dex 1 17 item_armor_perdex armour class per dex (8ths) min max armour only 0
att/str 1 17 item_tohit_perstr to hit per dex (8ths) min max weap only 0
att/dex 1 17 item_tohit_perDex to hit per dex (8ths) min max weap only 0
hp/vit 1 17 item_hp_pervitality (8ths) 0
mana/enr 1 17 item_mana_perenr (8ths) 0
cast/enr 1 17 item_fastercastrate_perenr (8ths) 0
block/dex 1 17 item_fasterblockrate_perdex (8ths) 0
move/vit 1 17 item_fastermovevelocity_pervitality (8ths) 0
swing/str 1 17 item_fasterswingvelocity_perstrength (8ths) 0
dmg-min%/str 1 17 item_mindamage_percent_perstr (8ths) 0
dmg-min%/dex 1 17 item_mindamage_percent_perdex (8ths) 0
dmg-min%/lvl 1 17 item_mindamage_percent_perlvl (8ths) 0
crush/str 1 17 item_crushingblow_perstr +% Chance of Crushing Blow per Player Level (8ths) none 0
crush/dex 1 17 item_crushingblow_perdex +% Chance of Crushing Blow per Player Level (8ths) none 0
coldskill 1 4 21 item_elemskill 1 item_elemskillcold none 0
lightningskill 1 2 21 item_elemskill 1 item_elemskilllight none 0
poisonskill 1 5 21 item_elemskill 1 item_elemskillpoison none 0
magicskill 1 3 21 item_elemskill 1 item_elemskillmagic none 0
ac%/str 1 17 item_armorpercent_perstr AC% per Player Level ac%/lvl (8ths) none 0
ac%/dex 1 17 item_armorpercent_perdex AC% per Player Level ac%/lvl (8ths) none 0
att%/str 1 17 item_tohitpercent_perstr Attack% per Player Level att%/lvl (8ths) none 0
att%/dex 1 17 item_tohitpercent_perdex Attack% per Player Level att%/lvl (8ths) none 0
swing/dex 1 17 item_fasterswingvelocity_perdex (8ths) 0
swing/lvl 1 17 item_fasterswingvelocity_perlvl (8ths) 0
block/str 1 17 item_fasterblockrate_perstr (8ths) 0
block/lvl 1 17 item_fasterblockrate_perlvl (8ths) 0
move/dex 1 17 item_fastermovevelocity_perdex (8ths) 0
move/lvl 1 17 item_fastermovevelocity_perlvl (8ths) 0
cast/lvl 1 17 item_fastercastrate_perlvl (8ths) 0
balance/vit 1 17 item_fasterhitrecovery_pervit (8ths) 0
balance/lvl 1 17 item_fasterhitrecovery_perlvl (8ths) 0
block%/dex 1 17 item_increasedblock_perdex (8ths) 0
block%/lvl 1 17 item_increasedblock_perlvl (8ths) 0
addxp/lvl 1 17 item_addexperience_perlvl additional xp gain 0
addxp/enr 1 17 item_addexperience_perenr additional xp gain 0
cheap/lvl 1 17 item_reducedprices_perlvl reduces vendor prices min max none 0
pierce/lvl 1 17 item_pierce_perlvl none 0
pierce/str 1 17 item_pierce_perstr none 0
all-zero-display 1 1 resmax_display_all_zero 0
res-all-hidden 1 1 magicresist_hidden 3 fireresist_hidden 3 lightresist_hidden 3 coldresist_hidden 3 poisonresist_hidden 3 hiddenresist_dummy 0
res-all-max-hidden 1 1 maxmagicresist_hidden 3 maxfireresist_hidden 3 maxlightresist_hidden 3 maxcoldresist_hidden 3 maxpoisonresist_hidden 3 hiddenresist_dummy 0
res-all-max% 1 1 maxmagres_percent 3 maxfireres_percent 3 maxlightres_percent 3 maxcoldres_percent 3 maxpoisres_percent 0
res-all/lvl 1 17 item_resist_cold_perlevel 3 item_resist_ltng_perlevel 3 item_resist_fire_perlevel 3 item_resist_pois_perlevel none 0
slow/lvl 1 17 item_slow_perlvl none 0
kick/str 1 17 item_kick_damage_perstr Kick Damage per Player Level (8ths) none 0
kick/dex 1 17 item_kick_damage_perdex Kick Damage per Player Level (8ths) none 0
red-dmg%/lvl 1 17 damageresist_perlvl none 0
red-dmg%/vit 1 17 damageresist_pervit none 0
red-mag/lvl 1 17 magic_damage_reduction_perlvl none 0
red-mag/enr 1 17 magic_damage_reduction_perenr none 0
res-mag/lvl 1 17 magicresist_perlvl none 0
res-mag/enr 1 17 magicresist_perenr none 0
stupidity/lvl 1 17 item_stupidity_perlvl none 0
freeze/lvl 1 17 item_freeze_perlvl chance in 128 length in frames none 0
freeze/enr 1 17 item_freeze_perenr chance in 128 length in frames none 0
res-pois-len/lvl 1 17 item_poisonlengthresist_perlvl none 0
res-pois-len/vit 1 17 item_poisonlengthresist_pervit none 0
res-curse/lvl 1 17 curse_resistance_perlvl 0
res-curse/enr 1 17 curse_resistance_perenr 0
res-curse-len 1 1 curse_resistance_length 1 curse_resistance 0
mana-kill/lvl 1 17 item_manaafterkill_perlvl none 0
mana-kill/enr 1 17 item_manaafterkill_perenr none 0
dmg-mana/lvl 1 17 item_damagetomana_perlvl none 0
dmg-mana/enr 1 17 item_damagetomana_perenr none 0
dmg-mana/mana 1 17 item_damagetomana_permana none 0
pruby 1 1 gembag_pruby 0
fruby 1 1 gembag_fruby 0
nruby 1 1 gembag_nruby 0
flruby 1 1 gembag_flruby 0
cruby 1 1 gembag_cruby 0
pamethyst 1 1 gembag_pamethyst 0
famethyst 1 1 gembag_famethyst 0
namethyst 1 1 gembag_namethyst 0
flamethyst 1 1 gembag_flamethyst 0
camethyst 1 1 gembag_camethyst 0
pdiamond 1 1 gembag_pdiamond 0
fdiamond 1 1 gembag_fdiamond 0
ndiamond 1 1 gembag_ndiamond 0
fldiamond 1 1 gembag_fldiamond 0
cdiamond 1 1 gembag_cdiamond 0
pemerald 1 1 gembag_pemerald 0
femerald 1 1 gembag_femerald 0
nemerald 1 1 gembag_nemerald 0
flemerald 1 1 gembag_flemerald 0
cemerald 1 1 gembag_cemerald 0
psapphire 1 1 gembag_psapphire 0
fsapphire 1 1 gembag_fsapphire 0
nsapphire 1 1 gembag_nsapphire 0
flsapphire 1 1 gembag_flsapphire 0
csapphire 1 1 gembag_csapphire 0
ptopaz 1 1 gembag_ptopaz 0
ftopaz 1 1 gembag_ftopaz 0
ntopaz 1 1 gembag_ntopaz 0
fltopaz 1 1 gembag_fltopaz 0
ctopaz 1 1 gembag_ctopaz 0
pskull 1 1 gembag_pskull 0
fskull 1 1 gembag_fskull 0
nskull 1 1 gembag_nskull 0
flskull 1 1 gembag_flskull 0
cskull 1 1 gembag_cskull 0
rbgr01 1 1 runebag_r01 0
rbgr02 1 1 runebag_r02 0
rbgr03 1 1 runebag_r03 0
rbgr04 1 1 runebag_r04 0
rbgr05 1 1 runebag_r05 0
rbgr06 1 1 runebag_r06 0
rbgr07 1 1 runebag_r07 0
rbgr08 1 1 runebag_r08 0
rbgr09 1 1 runebag_r09 0
rbgr10 1 1 runebag_r10 0
rbgr11 1 1 runebag_r11 0
rbgr12 1 1 runebag_r12 0
rbgr13 1 1 runebag_r13 0
rbgr14 1 1 runebag_r14 0
rbgr15 1 1 runebag_r15 0
rbgr16 1 1 runebag_r16 0
rbgr17 1 1 runebag_r17 0
rbgr18 1 1 runebag_r18 0
rbgr19 1 1 runebag_r19 0
rbgr20 1 1 runebag_r20 0
rbgr21 1 1 runebag_r21 0
rbgr22 1 1 runebag_r22 0
rbgr23 1 1 runebag_r23 0
rbgr24 1 1 runebag_r24 0
rbgr25 1 1 runebag_r25 0
rbgr26 1 1 runebag_r26 0
rbgr27 1 1 runebag_r27 0
rbgr28 1 1 runebag_r28 0
rbgr29 1 1 runebag_r29 0
rbgr30 1 1 runebag_r30 0
rbgr31 1 1 runebag_r31 0
rbgr32 1 1 runebag_r32 0
rbgr33 1 1 runebag_r33 0
magharv 1 1 magharv 0
showkills 1 17 display_kills 0
iforge 1 1 iforge 0

1433
UniqueItems.txt Normal file

File diff suppressed because it is too large Load Diff

308
Weapons.txt Normal file
View File

@ -0,0 +1,308 @@
name type type2 code alternateGfx namestr version compactsave rarity spawnable mindam maxdam 1or2handed 2handed 2handmindam 2handmaxdam minmisdam maxmisdam rangeadder speed StrBonus DexBonus reqstr reqdex durability nodurability level levelreq cost gamble cost magic lvl auto prefix OpenBetaGfx normcode ubercode ultracode wclass 2handedwclass component hit class invwidth invheight stackable minstack maxstack spawnstack flippyfile invfile uniqueinvfile setinvfile hasinv gemsockets gemapplytype special useable dropsound dropsfxframe usesound unique transparent transtbl quivered lightradius belt quest questdiffcheck missiletype durwarning qntwarning gemoffset bitfield1 CharsiMin CharsiMax CharsiMagicMin CharsiMagicMax CharsiMagicLvl GheedMin GheedMax GheedMagicMin GheedMagicMax GheedMagicLvl AkaraMin AkaraMax AkaraMagicMin AkaraMagicMax AkaraMagicLvl FaraMin FaraMax FaraMagicMin FaraMagicMax FaraMagicLvl LysanderMin LysanderMax LysanderMagicMin LysanderMagicMax LysanderMagicLvl DrognanMin DrognanMax DrognanMagicMin DrognanMagicMax DrognanMagicLvl HraltiMin HraltiMax HraltiMagicMin HraltiMagicMax HraltiMagicLvl AlkorMin AlkorMax AlkorMagicMin AlkorMagicMax AlkorMagicLvl OrmusMin OrmusMax OrmusMagicMin OrmusMagicMax OrmusMagicLvl ElzixMin ElzixMax ElzixMagicMin ElzixMagicMax ElzixMagicLvl AshearaMin AshearaMax AshearaMagicMin AshearaMagicMax AshearaMagicLvl CainMin CainMax CainMagicMin CainMagicMax CainMagicLvl HalbuMin HalbuMax HalbuMagicMin HalbuMagicMax HalbuMagicLvl JamellaMin JamellaMax JamellaMagicMin JamellaMagicMax JamellaMagicLvl LarzukMin LarzukMax LarzukMagicMin LarzukMagicMax LarzukMagicLvl DrehyaMin DrehyaMax DrehyaMagicMin DrehyaMagicMax DrehyaMagicLvl MalahMin MalahMax MalahMagicMin MalahMagicMax MalahMagicLvl Source Art Game Art Transform InvTrans SkipName NightmareUpgrade HellUpgrade Nameable PermStoreItem
Hand Axe axe hax hax hax 0 3 1 3 6 100 14 3 0 0 0 hax hax 9ha 7ha 1hs 1hs 5 1hsl 1 3 flphax invhax invhaxu invhaxu 1 4 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 0 0 3 2 2 0 axe 2ax 1 0
Axe axe axe axe axe 0 4 1 4 11 1 10 100 32 12 7 0 0 0 axe axe 9ax 7ax 1hs 1hs 5 1hsl 2 3 flpaxe invaxe invaxeu invaxeu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 2ax mpi 1 0
Double Axe axe 2ax axe 2ax 0 4 1 5 13 1 10 100 43 12 13 0 0 0 axe 2ax 92a 72a 1hs 1hs 5 1hsl 2 3 flp2ax inv2ax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 mpi wax 1 0
Military Pick axe mpi axe mpi 0 4 1 7 11 1 -10 100 49 33 13 19 0 0 0 axe mpi 9mp 7mp 1hs 1hs 5 1hsl 2 3 flpmpi invmpi invmpiu invmpiu 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 wax xxx 1 0
War Axe axe wax hax wax 0 4 1 10 18 2 100 67 13 25 0 0 0 hax wax 9wa 7wa 1hs 1hs 5 1hsl 2 3 flpwax invwax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Large Axe axe lax lax lax 0 4 1 1 6 13 1 -10 100 35 15 6 0 0 0 lax lax 9la 7la stf stf 5 2hsl 2 3 flplax invlax 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 btx gax 1 0
Broad Axe axe bax lax bax 0 4 1 1 10 18 1 100 48 18 12 0 0 0 lax bax 9ba 7ba stf stf 5 2hsl 2 3 flpbrx invbrx 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 gax gix 1 0
Battle Axe axe btx btx btx 0 4 1 1 12 32 1 10 100 54 20 17 0 0 0 lax btx 9bt 7bt stf stf 5 2hsl 2 3 flpbtx invbtx invbtxu invbtxu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 5 2 0 gix xxx 1 0
Great Axe axe gax btx gax 0 4 1 1 9 30 2 -10 100 63 39 25 23 0 0 0 lax gax 9ga 7ga stf stf 5 2hsl 2 4 flpgax invgax invgaxu invgaxu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Giant Axe axe gix gix gix 0 4 1 1 22 45 3 10 100 70 25 27 0 0 0 lax gix 9gi 7gi stf stf 5 2hsl 2 3 flpgix invgix 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Wand wand wnd wnd wnd 0 1 1 2 4 100 8 2 0 0 0 1 wnd wnd 9wn 7wn 1hs 1hs 5 1hss 1 2 flpwnd invwnd invwndu invwndu 1 3 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 ywn bwn 1 0
Yew Wand wand ywn ywn ywn 0 1 1 2 8 10 100 8 12 0 0 0 1 wnd ywn 9yw 7yw 1hs 1hs 5 1hss 1 2 flpywn invywn 1 3 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 bwn gwn 1 0
Bone Wand wand bwn bwn bwn 0 1 1 3 7 -20 100 8 18 0 0 0 1 wnd bwn 9bw 7bw 1hs 1hs 5 1hss 1 2 flpbwn invbwn invbwnu invbwnu 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Grim Wand wand gwn bwn gwn 0 1 1 5 11 100 8 26 0 0 0 1 wnd gwn 9gw 7gw 1hs 1hs 5 1hss 1 2 flpgwn invgwn 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Club club clb clb clb 0 1 1 1 6 -10 100 12 1 0 0 0 clb clb 9cl 7cl 1hs 1hs 5 club 1 3 flpclb invclb invclbu invclbu 1 4 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 5 2 0 mac mst 1 0
Scepter scep scp mac scp 0 2 1 6 11 100 25 25 3 0 0 0 mac scp 9sc 7sc 1hs 1hs 5 club 1 3 flpscp invscp 1 4 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 gsc wsp 1 0
Grand Scepter scep gsc mac gsc 0 4 1 8 18 1 10 100 37 30 15 0 0 0 mac gsc 9qs 7qs 1hs 1hs 5 club 1 3 flpgsc invgsc 1 5 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx wsp 1 0
War Scepter scep wsp whm wsp 0 4 1 10 17 1 -10 100 55 35 21 0 0 0 mac wsp 9ws 7ws 1hs 1hs 5 club 2 3 flpwsp invwsp 1 7 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Spiked Club club spc clb spc 0 3 1 5 8 1 100 18 4 0 0 0 clb spc 9sp 7sp 1hs 1hs 5 1hsl 1 3 flpspc invspc invspcu invspcu 1 4 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 2 2 0 mac mst 1 0
Mace mace mac mac mac 0 4 1 3 10 100 27 30 8 0 0 0 mac mac 9ma 7ma 1hs 1hs 5 1hsl 1 3 flpmac invmac 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 mst mau 1 0
Morning Star mace mst mac mst 0 4 1 7 16 1 10 100 36 36 13 0 0 0 mac mst 9mt 7mt 1hs 1hs 5 1hsl 1 3 flpmst invmst invmstu invmstu 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx fla 1 0
Flail mace fla fla fla 0 4 1 1 24 2 -10 100 41 35 15 19 0 0 0 mac fla 9fl 7fl 1hs 1hs 5 1hsl 2 3 flpfla invfla 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx gma 1 0
War Hammer hamm whm whm whm 0 4 1 19 29 20 110 53 28 25 0 0 0 mac whm 9wh 7wh 1hs 1hs 5 1hsl 2 3 flpwhm invwhm 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 178 7 0 0 3 2 2 0 mau gma 1 0
Maul hamm mau mau mau 0 4 1 1 30 43 1 10 110 69 30 21 0 0 0 mau mau 9m9 7m7 stf stf 5 2hsl 2 4 flpmau invmau invmauu invmauu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx mau 1 0
Great Maul hamm gma mau gma 0 3 1 1 38 58 2 20 110 99 30 32 0 0 0 mau gma 9gm 7gm stf stf 5 2hsl 2 3 flpgma invgma invgma invgma 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Short Sword swor ssd ssd ssd 0 3 1 2 7 100 12 1 0 0 0 ssd ssd 9ss 7ss 1hs 1hs 5 1hsl 1 3 flpssd invssd 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 scm wsd 1 0
Scimitar swor scm scm scm 0 3 1 2 6 -20 100 21 11 5 0 0 0 scm scm 9sm 7sm 1hs 1hs 5 1hsl 1 3 flpscm invscm invscmu invscmu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 sbr lsd 1 0
Saber swor sbr scm sbr 0 3 1 3 8 -10 100 25 25 16 8 0 0 0 scm sbr 9sb 7sb 1hs 1hs 5 1hsl 1 3 flpsbr invsbr inv9sbu inv9sbu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 flc bsd 1 0
Falchion swor flc flc flc 0 4 1 9 17 20 100 33 16 11 0 0 0 scm flc 9fc 7fc 1hs 1hs 5 1hsl 1 3 flpflc invflc invflcu invflcu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 bsd wsd 1 0
Crystal Sword swor crs crs crs 0 2 1 5 15 1 100 43 10 11 0 0 0 ssd crs 9cr 7cr 1hs 1hs 5 1hsl 2 3 flpcrs invcrs invcrsu invcrsu 1 7 0 0 item_sword 12 item_sword 0 0 5 0 5 0 0 7 0 0 1 1 8 0 xxx xxx 1 0
Broad Sword swor bsd bsd bsd 0 4 1 7 14 100 48 16 15 0 0 0 ssd bsd 9bs 7bs 1hs 1hs 5 1hsl 2 3 flpbsd invbsd invbsdu invbsdu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 lsd wsd 1 0
Long Sword swor lsd lsd lsd 0 4 1 3 19 1 -10 100 55 39 22 20 0 0 0 ssd lsd 9ls 7ls 1hs 1hs 5 1hsl 2 3 flplsd invlsd invlsdu invlsdu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 wsd wsd 1 0
War Sword swor wsd flc wsd 0 4 1 8 20 1 100 71 45 22 27 0 0 0 ssd wsd 9wd 7wd 1hs 1hs 5 1hsl 1 3 flpwsd invwsd 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Two-Handed Sword swor 2hs clm 2hs 0 4 1 2 9 1 1 8 17 2 100 35 27 22 10 0 0 0 clm 2hs 92h 72h 1hs 2hs 5 2hss 1 4 flp2hs inv2hs inv2hsu inv2hsu 1 5 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 clm gis 1 0
Claymore swor clm clm clm 0 4 1 5 12 1 1 13 30 2 10 100 47 25 17 0 0 0 clm clm 9cm 7cm 1hs 2hs 5 2hss 1 4 flpclm invclm 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 gis bsw 1 0
Giant Sword swor gis gsd gis 0 4 1 3 16 1 1 9 28 2 100 56 34 25 21 0 0 0 clm gis 9gs 7gs 1hs 2hs 5 2hss 1 4 flpgis invgis invgisu invgisu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 bsw flb 1 0
Bastard Sword swor bsw clm bsw 0 4 1 7 19 1 1 20 28 1 10 100 62 20 24 0 0 0 clm bsw 9b9 7b7 1hs 2hs 5 2hss 1 4 flpbsw invbsw invbswu invbswu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Flamberge swor flb clm flb 0 4 1 9 15 1 1 13 26 2 -10 100 70 49 25 27 0 0 0 clm flb 9fb 7fb 1hs 2hs 5 2hss 2 4 flpflb invflb 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Great Sword swor gsd gsd gsd 0 4 1 12 20 1 1 25 42 2 10 100 100 60 25 33 0 0 0 clm gsd 9gd 7gd 1hs 2hs 5 2hss 2 4 flpgsd invgsd invgsdu invgsdu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Dagger knif dgr dgr dgr 0 2 1 1 4 -20 75 75 8 3 0 0 0 dgr dgr 9dg 7dg 1ht 1ht 5 1ht 1 2 flpdgr invdgr 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 kri bld 1 0
Dirk knif dir dir dir 0 2 1 3 9 75 75 25 10 9 0 0 0 dgr dir 9di 7di 1ht 1ht 5 1ht 1 2 flpdir invdir 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 kri bld 1 0
Kriss knif kri dir kri 0 2 1 2 11 -20 75 75 45 12 17 0 0 0 dgr kri 9kr 7kr 1ht 1ht 5 1ht 1 3 flpkrs invkrs invkrsu invkrsu 1 5 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Blade knif bld dgr bld 0 3 1 4 15 -10 75 75 35 51 12 23 0 0 0 dgr bld 9bl 7bl 1ht 1ht 5 1ht 1 3 flpbld invbld 1 4 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Throwing Knife tkni tkf dgr tkf 0 2 1 2 3 4 9 75 75 21 9 2 0 0 0 dgr tkf 9tk 7tk 1ht 1ht 5 1ht 1 2 1 40 160 75 flptkn invtkn 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 bkf bkf 1 0
Throwing Axe taxe tax hax tax 0 2 1 4 7 8 12 10 75 75 40 12 7 0 0 0 hax tax 9ta 7ta 1hs 1hs 5 1hsl 1 2 1 16 130 32 flptax invtax 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 5 0 3 2 2 0 bal bal 1 0
Balanced Knife tkni bkf dgr bkf 0 2 1 1 8 6 11 -20 75 75 51 12 13 0 0 0 dgr bkf 9bk 7bk 1ht 1ht 5 1ht 1 2 1 30 160 60 flpbkf invbkf 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 xxx xxx 1 0
Balanced Axe taxe bal hax bal 0 2 1 5 10 12 15 -10 75 75 57 15 16 0 0 0 hax bal 9b8 7b8 1hs 1hs 5 1hsl 2 3 1 16 130 24 flpbal invbal 1 4 0 primarily thrown 0 item_sword 12 item_sword 0 0 5 0 0 0 35 7 5 0 3 2 2 0 xxx xxx 1 0
Javelin jave jav jav jav 0 4 1 1 5 6 14 15 2 -10 75 75 12 1 0 0 0 jav jav 9ja 7ja 1ht 1ht 5 1ht 1 3 1 30 60 60 flpjav invjav 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 1 7 2 0 3 5 8 0 glv glv 1 0
Pilum jave pil pil pil 0 4 1 4 9 7 20 15 2 75 75 45 9 10 0 0 0 jav pil 9pi 7pi 1ht 1ht 5 1ht 1 3 1 25 50 50 flppil invpil 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 371 7 2 0 3 5 8 0 tsp tsp 1 0
Short Spear jave ssp jav ssp 0 4 1 2 13 10 22 15 2 10 75 75 40 40 9 15 0 0 0 jav ssp 9s9 7s7 1ht 1ht 5 1ht 1 3 1 20 40 40 flpssp invssp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 1 7 2 0 3 5 8 0 tsp tsp 1 0
Glaive jave glv glv glv 0 4 1 5 17 16 22 15 2 20 75 75 52 35 12 23 0 0 0 jav glv 9gl 7gl 1ht 1ht 5 1ht 1 4 1 15 40 40 flpglv invglv 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 1 2 0 xxx xxx 1 0
Throwing Spear jave tsp pil tsp 0 4 1 5 15 12 30 15 2 -10 75 75 65 12 29 0 0 0 jav tsp 9ts 7ts 1ht 1ht 5 1ht 1 4 1 32 80 80 flptsp invtsp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 371 7 2 0 3 1 2 0 xxx xxx 1 0
Spear spea spr spr spr 0 4 1 1 3 15 15 3 -10 100 20 15 5 0 0 0 spr spr 9sr 7sr 2ht 2ht 5 2ht 2 4 flpspr invspr 1 5 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 spt pik 1 0
Trident spea tri tri tri 0 4 1 1 9 15 15 3 100 38 24 18 9 0 0 0 spr tri 9tr 7tr 2ht 2ht 5 2ht 2 4 flptri invtri invtriu invtriu 1 6 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 spt pik 1 0
Brandistock spea brn brn brn 0 4 1 1 7 17 15 4 -20 100 40 50 14 16 0 0 0 spr brn 9br 7br 2ht 2ht 5 2ht 2 4 flpbrn invbrn 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx pik 1 0
Spetum spea spt tri spt 0 4 1 1 15 23 15 4 100 54 35 14 20 0 0 0 spr spt 9st 7st 2ht 2ht 5 2ht 2 4 flpspt invspt 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Pike spea pik pik pik 0 4 1 1 14 63 15 4 20 100 60 45 13 24 0 0 0 spr pik 9p9 7p7 2ht 2ht 5 2ht 2 4 flppik invpik 1 7 0 3 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Bardiche pole bar hal bar 0 4 1 1 1 27 2 10 100 40 25 5 0 0 0 hal bar 9b7 7o7 stf stf 5 2hsl 2 4 flpbar invbar 1 5 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 hal wsc 1 0
Voulge pole vou hal vou 0 4 1 1 6 21 2 100 50 25 11 0 0 0 hal vou 9vo 7vo stf stf 5 2hsl 2 4 flpvou invvou 1 6 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 hal wsc 1 0
Scythe pole scy scy scy 0 4 1 1 8 20 1 -10 100 41 41 33 15 0 0 0 hal scy 9s8 7s8 stf stf 5 2hsl 2 4 flpscy invscy invscyu invscyu 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 hal wsc 1 0
Poleaxe pole pax hal pax 0 4 1 1 18 39 3 10 100 62 33 21 0 0 0 hal pax 9pa 7pa stf stf 5 2hsl 2 4 flppax invpax 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Halberd pole hal pax hal 0 4 1 1 12 45 4 100 75 47 28 29 0 0 0 hal hal 9h9 7h7 stf stf 5 2hsl 2 4 flphal invhal 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
War Scythe pole wsc scy wsc 0 3 1 1 15 36 4 -10 100 80 80 28 34 0 0 0 hal wsc 9wc 7wc stf stf 5 2hsl 2 4 flpwsc invwsc 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Short Staff staf sst bst sst 0 2 1 1 1 5 1 -10 100 10 1 0 0 0 1 bst sst 8ss 6ss stf stf 5 club 1 3 flpsst invsst 1 4 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 cst bst 1 0
Long Staff staf lst sst lst 0 2 1 1 2 8 1 100 15 8 0 0 0 1 bst lst 8ls 6ls stf stf 5 staf 1 4 flplst invlst 1 5 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 cst wst 1 0
Gnarled Staff staf cst cst cst 0 2 1 1 4 12 1 10 100 18 12 0 0 0 1 bst cst 8cs 6cs stf stf 5 staf 1 4 flpcst invcst invcstu invcstu 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx wst 1 0
Battle Staff staf bst sst bst 0 2 1 1 6 13 1 100 20 17 0 0 0 1 bst bst 8bs 6bs stf stf 5 staf 1 4 flpbst invbst 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
War Staff staf wst lst wst 0 2 1 1 12 28 1 20 100 25 24 0 0 0 1 bst wst 8ws 6ws stf stf 5 staf 2 4 flpwst invwst 1 7 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Short Bow bow sbw sbw sbw 0 2 1 1 1 4 13 5 100 15 10 1 1 0 0 0 sbw sbw 8sb 6sb bow bow 6 bow 2 3 flpsbw invsbw 1 5 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 hbw cbw 1 0
Hunter's Bow bow hbw sbw hbw 0 2 1 1 2 6 13 -10 100 28 16 1 5 0 0 0 sbw hbw 8hb 6hb bow bow 6 bow 2 3 flphbw invhbw 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 lbw sbb 1 0
Long Bow bow lbw lbw lbw 0 2 1 1 3 10 13 100 22 19 14 1 8 0 0 0 sbw lbw 8lb 6lb bow bow 6 bow 2 4 flplbw invlbw 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 cbw lbb 1 0
Composite Bow bow cbw lbw cbw 0 2 1 1 4 8 13 -10 100 25 35 18 1 12 0 0 0 sbw cbw 8cb 6cb bow bow 6 bow 2 3 flpcbw invcbw invcbwu invcbwu 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 sbb swb 1 0
Short Battle Bow bow sbb sbb sbb 0 2 1 1 5 11 13 100 30 40 20 1 18 0 0 0 sbw sbb 8s8 6s7 bow bow 6 bow 2 3 flpsbb invsbb invsbbu invsbbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 lbb lwb 1 0
Long Battle Bow bow lbb lbb lbb 0 2 1 1 3 18 13 10 100 40 50 22 1 23 0 0 0 sbw lbb 8l8 6l7 bow bow 6 bow 2 4 flplbb invlbb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 swb lwb 1 0
Short War Bow bow swb sbb swb 0 2 1 1 6 14 13 100 35 55 24 1 27 0 0 0 sbw swb 8sw 6sw bow bow 6 bow 2 3 flpswb invswb invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 lwb xxx 1 0
Long War Bow bow lwb lbb lwb 0 2 1 1 3 23 13 10 100 50 65 28 1 31 0 0 0 sbw lwb 8lw 6lw bow bow 6 bow 2 4 flplwb invlwb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Light Crossbow xbow lxb lxb lxb 0 2 1 1 6 9 -10 100 21 27 15 1 6 0 0 0 lxb lxb 8lx 6lx xbw xbw 5 xbow 2 3 flplxb invlxb invlxbu invlxbu 1 5 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 mxb rxb 1 0
Crossbow xbow mxb lxb mxb 0 2 1 1 9 16 100 40 33 20 1 15 0 0 0 lxb mxb 8mx 6mx xbw xbw 5 xbow 2 3 flpmxb invmxb invmxbu invmxbu 1 6 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 hxb hxb 1 0
Heavy Crossbow xbow hxb hxb hxb 0 2 1 1 14 26 10 100 60 40 25 1 24 0 0 0 lxb hxb 8hx 6hx xbw xbw 5 xbow 2 4 flphxb invhxb invhxbu invhxbu 1 7 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Repeating Crossbow xbow rxb hxb rxb 0 2 1 1 6 12 -40 100 40 50 20 1 33 0 0 0 lxb rxb 8rx 6rx xbw xbw 5 xbow 2 3 flprxb invrxb invrxbu invrxbu 1 7 0 fires 5 shots before reload 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Rancid Gas Potion tpot gps gpl gps 0 1 0 1 1 1 1 0 0 0 gpl gps 1ht 1ht 5 hth 1 1 1 1 6 4 flpgpl invgpl 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 49 7 3 0 0 0 0 0 xxx xxx 0 1
Oil Potion tpot ops opl ops 0 1 0 1 1 1 1 0 0 0 opl ops 1ht 1ht 5 hth 1 1 1 1 6 4 flpopl invopl 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 46 7 3 0 0 0 0 0 xxx xxx 0 1
Choking Gas Potion tpot gpm gps gpm 0 1 0 1 1 1 1 0 0 0 gps gpm 1ht 1ht 5 hth 1 1 1 1 6 4 flpgps invgpm 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 48 7 3 0 0 0 0 0 xxx xxx 0 1
Exploding Potion tpot opm opl opm 0 1 0 1 1 1 1 0 0 0 opl opm 1ht 1ht 5 hth 1 1 1 1 6 4 flpops invopm 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 45 7 3 0 0 0 0 0 xxx xxx 0 1
Strangling Gas Potion tpot gpl gps gpl 0 1 0 1 1 1 1 0 0 0 gps gpl 1ht 1ht 5 hth 1 1 1 1 6 4 flpgps invgps 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 47 7 3 0 0 0 0 0 xxx xxx 0 1
Fulminating Potion tpot opl ops opl 0 1 0 1 1 1 1 0 0 0 ops opl 1ht 1ht 5 hth 1 1 1 1 6 4 flpops invops 1 0 0 item_potion 14 item_potion 0 0 5 0 1 0 44 7 3 0 0 0 0 0 xxx xxx 0 1
decoy dagger knif d33 dgr d33 0 1 2 -20 100 15 20 5 0 0 0 0 dgr d33 1ht 1ht 5 1ht 1 2 flpd33 invd33 1 0 0 item_smallmetalweapon 12 item_smallmetalweapon 1 0 5 0 0 0 18 1 0 7 0 0 3 1 2 0 xxx xxx 0 0
Gidbinn knif g33 dgr g33 0 3 7 -20 100 15 25 15 0 0 0 0 dgr g33 1ht 1ht 5 1ht 1 2 flpg33 invg33 1 0 0 item_smallmetalweapon 12 item_smallmetalweapon 1 0 5 0 0 0 18 1 0 7 0 0 3 1 2 0 xxx xxx 0 0
Wirt's Leg club leg clb leg 0 2 8 -10 100 33 0 0 0 0 clb leg 1hs 1hs 5 1hsl 1 3 flpleg invleg 1 5 0 0 item_staff 12 item_staff 0 0 5 0 0 0 1 1 0 7 0 0 1 5 8 0 xxx xxx 1 0
Horadric Malus hamm hdm whm hdm 0 6 15 20 100 15 15 28 0 0 0 0 whm hdm 1hs 1hs 5 1hsl 1 2 flphmr invhmr 1 0 0 item_sword 12 item_sword 1 0 5 0 0 0 4 1 0 7 0 0 0 2 2 0 xxx xxx 0 0
Hellforge Hammer hamm hfh whm hfh 0 6 15 100 28 0 0 0 0 whm hfh 1hs 1hs 5 1hsl 2 3 flphmr invhfh 1 7 0 0 item_sword 12 item_sword 1 0 5 0 0 0 25 1 0 7 0 0 1 2 2 1 xxx xxx 0 0
Horadric Staff staf hst bst hst 0 1 12 20 2 100 30 25 0 0 0 0 bst hst stf stf 5 staf 1 4 flphst invhst 1 0 magically charged 0 item_staff 12 item_staff 1 0 5 0 0 0 10 1 0 7 0 0 5 5 8 1 xxx xxx 0 0
Staff of the Kings staf msf bst msf 0 1 10 15 2 100 25 23 0 0 0 0 bst msf stf stf 5 staf 1 3 flpmsf invmsf 1 0 magically charged 0 item_staff 12 item_staff 1 0 5 0 0 0 10 1 0 7 0 0 5 5 8 1 xxx xxx 0 0
Hatchet axe 9ha hax 9ha 0 3 1 10 21 100 25 25 14 31 19 0 0 hax hax 9ha 7ha 1hs 1hs 5 1hsl 1 3 flphax invhax invhaxu invhaxu 1 4 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 0 0 3 5 2 0 xxx xxx 1 0
Cleaver axe 9ax axe 9ax 0 4 1 10 33 1 10 100 68 12 34 22 0 0 axe axe 9ax 7ax 1hs 1hs 5 1hsl 2 3 flpaxe invaxe invaxeu invaxeu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
Twin Axe axe 92a axe 92a 0 4 1 13 38 1 10 100 85 12 39 25 0 0 axe 2ax 92a 72a 1hs 1hs 5 1hsl 2 3 flp2ax inv2ax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
Crowbill axe 9mp axe 9mp 0 4 1 14 34 1 -10 100 94 70 13 43 25 0 0 axe mpi 9mp 7mp 1hs 1hs 5 1hsl 2 3 flpmpi invmpi invmpiu invmpiu 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Naga axe 9wa hax 9wa 0 4 1 16 45 2 100 121 13 48 25 0 0 hax wax 9wa 7wa 1hs 1hs 5 1hsl 2 3 flpwax invwax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Military Axe axe 9la lax 9la 0 4 1 1 14 34 1 -10 100 73 15 34 22 0 0 lax lax 9la 7la stf stf 5 2hsl 2 3 flplax invlax 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Bearded Axe axe 9ba lax 9ba 0 4 1 1 21 49 1 100 92 18 38 25 0 0 lax bax 9ba 7ba stf stf 5 2hsl 2 3 flpbrx invbrx 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Tabar axe 9bt btx 9bt 0 4 1 1 24 77 1 10 100 101 20 42 25 0 0 btx btx 9bt 7bt stf stf 5 2hsl 2 3 flpbtx invbtx inv9btu inv9btu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
Gothic Axe axe 9ga btx 9ga 0 4 1 1 18 70 2 -10 100 115 79 25 46 25 0 0 btx gax 9ga 7ga stf stf 5 2hsl 2 4 flpgax invgax invgaxu invgaxu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Ancient Axe axe 9gi gix 9gi 0 4 1 1 43 85 3 10 100 125 25 51 25 0 0 gix gix 9gi 7gi stf stf 5 2hsl 2 3 flpgix invgix inv9giu inv9giu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Burnt Wand wand 9wn wnd 9wn 0 1 1 8 18 100 25 8 31 19 0 0 1 wnd wnd 9wn 7wn 1hs 1hs 5 1hss 1 2 flpwnd invwnd 1 3 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Petrified Wand wand 9yw ywn 9yw 0 1 1 8 24 10 100 25 8 38 25 0 0 1 ywn ywn 9yw 7yw 1hs 1hs 5 1hss 1 2 flpywn invywn 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Tomb Wand wand 9bw bwn 9bw 0 1 1 10 22 -20 100 25 8 43 25 0 0 1 bwn bwn 9bw 7bw 1hs 1hs 5 1hss 1 2 flpbwn invbwn invbwnu invbwnu 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Grave Wand wand 9gw bwn 9gw 0 1 1 13 29 100 25 8 49 25 0 0 1 bwn gwn 9gw 7gw 1hs 1hs 5 1hss 1 2 flpgwn invgwn inv9gwu inv9gwu 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Cudgel club 9cl clb 9cl 0 1 1 6 21 -10 100 25 12 30 18 0 0 clb clb 9cl 7cl 1hs 1hs 5 club 1 3 flpclb invclb invclbu invclbu 1 4 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 5 2 0 xxx xxx 1 0
Rune Scepter scep 9sc mac 9sc 0 2 1 13 24 100 58 25 31 19 0 0 mac scp 9sc 7sc 1hs 1hs 5 club 1 3 flpscp invscp 1 4 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Holy Water Sprinkler scep 9qs mac 9qs 0 4 1 14 36 1 10 100 76 30 40 25 0 0 mac gsc 9qs 7qs 1hs 1hs 5 club 1 3 flpgsc invgsc 1 5 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Divine Scepter scep 9ws whm 9ws 0 4 1 16 38 1 -10 100 103 35 45 25 0 0 whm wsp 9ws 7ws 1hs 1hs 5 club 2 3 flpwsp invwsp 1 7 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Barbed Club club 9sp clb 9sp 0 3 1 13 25 1 100 30 18 32 20 0 0 clb spc 9sp 7sp 1hs 1hs 5 1hsl 1 3 flpspc invspc invspcu invspcu 1 5 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 2 2 0 xxx xxx 1 0
Flanged Mace mace 9ma mac 9ma 0 4 1 15 23 100 61 30 35 23 0 0 mac mac 9ma 7ma 1hs 1hs 5 1hsl 1 3 flpmac invmac 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Jagged Star mace 9mt mac 9mt 0 4 1 20 31 1 10 100 74 36 39 25 0 0 mac mst 9mt 7mt 1hs 1hs 5 1hsl 1 3 flpmst invmst invmstu invmstu 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Knout mace 9fl fla 9fl 0 4 1 13 35 2 -10 100 82 73 15 43 25 0 0 fla fla 9fl 7fl 1hs 1hs 5 1hsl 2 3 flpfla invfla 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Battle Hammer hamm 9wh whm 9wh 0 4 1 35 58 20 110 100 28 48 25 0 0 whm whm 9wh 7wh 1hs 1hs 5 1hsl 2 3 flpwhm invwhm 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 178 7 0 0 3 2 2 0 xxx xxx 1 0
War Club hamm 9m9 mau 9m9 0 4 1 1 53 78 1 10 110 124 30 45 25 0 0 mau mau 9m9 7m7 stf stf 5 2hsl 2 4 flpmau invmau 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Martel de Fer hamm 9gm mau 9gm 0 3 1 1 61 99 2 20 110 169 30 53 25 0 0 mau gma 9gm 7gm stf stf 5 2hsl 2 3 flpgma invgma inv9gmu inv9gmu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Gladius swor 9ss ssd 9ss 0 3 1 8 22 100 25 12 30 18 0 0 ssd ssd 9ss 7ss 1hs 1hs 5 1hsl 1 3 flpssd invssd 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Cutlass swor 9sm scm 9sm 0 3 1 8 21 -30 100 25 52 11 43 25 0 0 scm scm 9sm 7sm 1hs 1hs 5 1hsl 1 3 flpscm invscm invscmu invscmu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Shamshir swor 9sb scm 9sb 0 3 1 10 24 -10 100 58 58 16 35 23 0 0 scm sbr 9sb 7sb 1hs 1hs 5 1hsl 1 3 flpsbr invsbr invsbru invsbru 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Tulwar swor 9fc flc 9fc 0 4 1 16 35 20 100 70 42 16 37 25 0 0 flc flc 9fc 7fc 1hs 1hs 5 1hsl 1 3 flpflc invflc 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Dimensional Blade swor 9cr crs 9cr 0 2 1 13 35 1 100 85 60 10 37 25 0 0 crs crs 9cr 7cr 1hs 1hs 5 1hsl 2 3 flpcrs invcrs inv9cru inv9cru 1 7 0 0 item_sword 12 item_sword 0 0 5 0 5 0 0 7 0 0 1 1 8 0 xxx xxx 1 0
Battle Sword swor 9bs bsd 9bs 0 4 1 16 34 100 92 43 16 40 25 0 0 bsd bsd 9bs 7bs 1hs 1hs 5 1hsl 2 3 flpbsd invbsd 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Rune Sword swor 9ls lsd 9ls 0 4 1 10 42 1 -10 100 103 79 22 44 25 0 0 lsd lsd 9ls 7ls 1hs 1hs 5 1hsl 2 3 flplsd invlsd inv9lsu inv9lsu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Ancient Sword swor 9wd flc 9wd 0 4 1 18 43 1 100 127 88 22 49 25 0 0 flc wsd 9wd 7wd 1hs 1hs 5 1hsl 1 3 flpwsd invwsd 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Espadon swor 92h clm 92h 0 4 1 8 26 1 1 18 40 2 100 73 61 22 37 25 0 0 clm 2hs 92h 72h 1hs 2hs 5 2hss 1 4 flp2hs inv2hs inv2hsu inv2hsu 1 5 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Dacian Falx swor 9cm clm 9cm 0 4 1 13 30 1 1 26 61 2 10 100 91 20 25 42 25 0 0 clm clm 9cm 7cm 1hs 2hs 5 2hss 1 4 flpclm invclm 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Tusk Sword swor 9gs gsd 9gs 0 4 1 10 37 1 1 19 58 2 100 104 71 25 45 25 0 0 gsd gis 9gs 7gs 1hs 2hs 5 2hss 1 4 flpgis invgis invgisu invgisu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Gothic Sword swor 9b9 clm 9b9 0 4 1 14 40 1 1 39 60 1 10 100 113 20 20 48 25 0 0 clm bsw 9b9 7b7 1hs 2hs 5 2hss 1 4 flpbsw invbsw invbswu invbswu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Zweihander swor 9fb clm 9fb 0 4 1 19 35 1 1 29 54 2 -10 100 125 94 25 49 25 0 0 clm flb 9fb 7fb 1hs 2hs 5 2hss 2 4 flpflb invflb inv9fbu inv9fbu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Executioner Sword swor 9gd gsd 9gd 0 4 1 24 40 1 1 47 80 2 10 100 170 110 25 54 25 0 0 gsd gsd 9gd 7gd 1hs 2hs 5 2hss 2 4 flpgsd invgsd invgsdu invgsdu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Poignard knif 9dg dgr 9dg 0 2 1 6 18 -20 75 75 25 8 31 19 0 0 dgr dgr 9dg 7dg 1ht 1ht 5 1ht 1 2 flpdgr invdgr 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Rondel knif 9di dir 9di 0 2 1 10 26 75 75 25 58 10 36 24 0 0 dir dir 9di 7di 1ht 1ht 5 1ht 1 2 flpdir invdir 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Cinquedeas knif 9kr dir 9kr 0 2 1 15 31 -20 75 75 25 88 12 42 25 0 0 dir kri 9kr 7kr 1ht 1ht 5 1ht 1 3 flpkrs invkrs invkrsu invkrsu 1 5 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Stilleto knif 9bl dgr 9bl 0 3 1 19 36 -10 75 75 47 97 12 46 25 0 0 dgr bld 9bl 7bl 1ht 1ht 5 1ht 1 3 flpbld invbld inv9blu inv9blu 1 4 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Battle Dart tkni 9tk dgr 9tk 0 2 1 8 16 11 24 15 75 75 25 52 5 31 19 0 0 dgr tkf 9tk 7tk 1ht 1ht 5 1ht 1 2 1 40 160 75 flptkn invtkn 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 xxx xxx 1 0
Francisca taxe 9ta hax 9ta 0 2 1 11 22 18 33 15 10 75 75 25 80 9 34 22 0 0 hax tax 9ta 7ta 1hs 1hs 5 1hsl 1 2 1 16 130 32 flptax invtax 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 5 0 3 5 2 0 xxx xxx 1 0
War Dart tkni 9bk dgr 9bk 0 2 1 6 24 14 27 15 -20 75 75 25 97 12 39 25 0 0 dgr bkf 9bk 7bk 1ht 1ht 5 1ht 1 2 1 30 160 60 flpbkf invbkf 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 xxx xxx 1 0
Hurlbat taxe 9b8 hax 9b8 0 2 1 13 27 24 34 15 -10 75 75 25 106 9 41 25 0 0 hax bal 9b8 7b8 1hs 1hs 5 1hsl 2 3 1 16 130 24 flpbal invbal 1 4 0 primarily thrown 0 item_sword 12 item_sword 0 0 5 0 0 0 35 7 5 0 3 2 2 0 xxx xxx 1 0
War Javelin jave 9ja jav 9ja 0 4 1 6 19 14 32 15 2 -10 75 75 25 25 10 30 18 0 0 jav jav 9ja 7ja 1ht 1ht 5 1ht 1 3 1 30 60 60 flpjav invjav 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 1 7 2 0 3 5 8 0 xxx xxx 1 0
Great Pilum jave 9pi pil 9pi 0 4 1 11 26 16 42 15 2 75 75 25 88 11 37 25 0 0 pil pil 9pi 7pi 1ht 1ht 5 1ht 1 3 1 25 50 50 flppil invpil 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 5 8 0 xxx xxx 1 0
Simbilan jave 9s9 jav 9s9 0 4 1 8 32 27 50 15 2 10 75 75 80 80 11 40 25 0 0 jav ssp 9s9 7s7 1ht 1ht 5 1ht 1 3 1 20 40 40 flpssp invssp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 5 8 0 xxx xxx 1 0
Spiculum jave 9gl glv 9gl 0 4 1 13 38 32 60 15 2 20 75 75 98 73 12 46 25 0 0 glv glv 9gl 7gl 1ht 1ht 5 1ht 1 4 1 8 20 20 flpglv invglv 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 1 2 0 xxx xxx 1 0
Harpoon jave 9ts pil 9ts 0 4 1 13 35 18 54 15 2 -10 75 75 25 118 14 51 25 0 0 pil tsp 9ts 7ts 1ht 1ht 5 1ht 1 4 1 32 80 80 flptsp invtsp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 1 2 0 xxx xxx 1 0
War Spear spea 9sr spr 9sr 0 4 1 1 10 37 3 -10 100 25 25 15 33 21 0 0 spr spr 9sr 7sr 2ht 2ht 5 2ht 2 4 flpspr invspr 1 5 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Fuscina spea 9tr tri 9tr 0 4 1 1 19 37 3 100 77 25 18 36 24 0 0 tri tri 9tr 7tr 2ht 2ht 5 2ht 2 4 flptri invtri invtriu invtriu 1 6 0 2 square reach 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
War Fork spea 9br brn 9br 0 4 1 1 16 40 4 -20 100 80 95 14 41 25 0 0 brn brn 9br 7br 2ht 2ht 5 2ht 2 4 flpbrn invbrn inv9bru inv9bru 1 7 0 2 square reach 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Yari spea 9st tri 9st 0 4 1 1 29 59 4 100 101 14 44 25 0 0 tri spt 9st 7st 2ht 2ht 5 2ht 2 4 flpspt invspt 1 7 0 2 square reach 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Lance spea 9p9 pik 9p9 0 4 1 1 27 114 4 20 100 110 88 13 47 25 0 0 pik pik 9p9 7p7 2ht 2ht 5 2ht 2 4 flppik invpik 1 7 0 3 square reach 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Lochaber Axe pole 9b7 hal 9b7 0 4 1 1 6 58 2 10 100 80 25 33 21 0 0 hal bar 9b7 7o7 stf stf 5 2hsl 2 4 flpbar invbar 1 5 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Bill pole 9vo hal 9vo 0 4 1 1 14 53 2 100 95 25 37 25 0 0 pax vou 9vo 7vo stf stf 5 2hsl 2 4 flpvou invvou 1 6 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Battle Scythe pole 9s8 scy 9s8 0 4 1 1 18 45 1 -10 100 82 82 33 40 25 0 0 scy scy 9s8 7s8 stf stf 5 2hsl 2 4 flpscy invscy inv9s8u inv9s8u 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Partizan pole 9pa hal 9pa 0 4 1 1 34 75 3 10 100 113 67 33 35 23 0 0 pax pax 9pa 7pa stf stf 5 2hsl 2 4 flppax invpax 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Bec-de-Corbin pole 9h9 pax 9h9 0 4 1 1 13 85 4 100 133 91 28 51 25 0 0 hal hal 9h9 7h7 stf stf 5 2hsl 2 4 flphal invhal 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Grim Scythe pole 9wc scy 9wc 0 3 1 1 30 70 4 -10 100 140 140 28 55 25 0 0 scy wsc 9wc 7wc stf stf 5 2hsl 2 4 flpwsc invwsc 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Jo Staff staf 8ss bst 8ss 0 2 1 1 6 21 1 -10 100 25 10 30 18 0 0 1 bst sst 8ss 6ss stf stf 5 club 1 3 flpsst invsst 1 4 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Quarterstaff staf 8ls sst 8ls 0 2 1 1 8 26 1 100 25 15 35 23 0 0 1 sst lst 8ls 6ls stf stf 5 staf 1 4 flplst invlst 1 5 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Cedar Staff staf 8cs cst 8cs 0 2 1 1 11 32 1 10 100 25 18 38 25 0 0 1 cst cst 8cs 6cs stf stf 5 staf 1 4 flpcst invcst invcstu invcstu 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Gothic Staff staf 8bs sst 8bs 0 2 1 1 14 34 1 100 25 20 42 25 0 0 1 sst bst 8bs 6bs stf stf 5 staf 1 4 flpbst invbst 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Rune Staff staf 8ws lst 8ws 0 2 1 1 24 58 1 20 100 25 25 47 25 0 0 1 lst wst 8ws 6ws stf stf 5 staf 2 4 flpwst invwst inv8wsu inv8wsu 1 7 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Edge Bow bow 8sb sbw 8sb 0 2 1 1 6 19 13 5 100 25 43 10 1 30 18 0 0 sbw sbw 8sb 6sb bow bow 6 bow 2 3 flpsbw invsbw 1 5 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Razor Bow bow 8hb sbw 8hb 0 2 1 1 8 22 13 -10 100 25 62 16 1 33 21 0 0 sbw hbw 8hb 6hb bow bow 6 bow 2 3 flphbw invhbw 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Cedar Bow bow 8lb lbw 8lb 0 2 1 1 10 29 13 100 53 49 14 1 35 23 0 0 lbw lbw 8lb 6lb bow bow 6 bow 2 4 flplbw invlbw inv8lbu inv8lbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Double Bow bow 8cb lbw 8cb 0 2 1 1 11 26 13 -10 100 58 73 18 1 39 25 0 0 lbw cbw 8cb 6cb bow bow 6 bow 2 3 flpcbw invcbw invcbwu invcbwu 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Short Siege Bow bow 8s8 sbb 8s8 0 2 1 1 13 30 13 100 65 80 20 1 43 25 0 0 sbb sbb 8s8 6s7 bow bow 6 bow 2 3 flpsbb invsbb inv8s8u inv8s8u 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Long Siege Bow bow 8l8 lbb 8l8 0 2 1 1 10 42 13 10 100 80 95 22 1 46 25 0 0 lbb lbb 8l8 6l7 bow bow 6 bow 2 4 flplbb invlbb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Rune Bow bow 8sw sbb 8sw 0 2 1 1 14 35 13 100 73 103 24 1 49 25 0 0 sbb swb 8sw 6sw bow bow 6 bow 2 3 flpswb invswb invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Gothic Bow bow 8lw lbb 8lw 0 2 1 1 10 50 13 10 100 95 118 28 1 52 25 0 0 lbb lwb 8lw 6lw bow bow 6 bow 2 4 flplwb invlwb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Arbalest xbow 8lx lxb 8lx 0 2 1 1 14 27 -10 100 52 61 15 1 34 22 0 0 lxb lxb 8lx 6lx xbw xbw 5 xbow 2 3 flplxb invlxb inv8lxu inv8lxu 1 5 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Siege Crossbow xbow 8mx lxb 8mx 0 2 1 1 20 42 100 80 70 20 1 40 25 0 0 lxb mxb 8mx 6mx xbw xbw 5 xbow 2 3 flpmxb invmxb inv8mxu inv8mxu 1 6 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Balista xbow 8hx hxb 8hx 0 2 1 1 33 55 10 100 110 80 25 1 47 25 0 0 hxb hxb 8hx 6hx xbw xbw 5 xbow 2 4 flphxb invhxb invhxbu invhxbu 1 7 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Chu-Ko-Nu xbow 8rx hxb 8rx 0 2 1 1 14 32 -60 100 80 95 20 1 54 25 0 0 hxb rxb 8rx 6rx xbw xbw 5 xbow 2 3 flprxb invrxb invrxbu invrxbu 1 7 0 fires 5 shots before reload 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
KhalimFlail mace qf1 fla qf1 0 1 15 -10 100 41 35 15 0 0 0 0 fla qf1 1hs 1hs 5 1hsl 2 3 flpfla invqf1 1 5 0 0 item_sword 12 item_sword 1 0 5 0 0 0 17 1 0 7 0 0 1 2 2 1 xxx xxx 0 0
SuperKhalimFlail mace qf2 fla qf2 0 1 15 -10 100 15 0 0 0 0 fla qf2 1hs 1hs 5 1hsl 2 3 flpfla invqf2 1 5 0 0 item_sword 12 item_sword 1 0 5 0 0 0 17 1 0 7 0 0 1 2 2 1 xxx xxx 0 0
Expansion
Katar h2h ktr ktr ktr 100 2 1 4 7 14 1 -10 75 75 20 20 24 1 0 0 0 ktr ktr 9ar 7ar ht1 ht1 5 1hsl 1 3 flpktr invktr 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 wrb btl 1 0
Wrist Blade h2h wrb ktr wrb 100 2 1 5 9 14 1 75 75 33 33 26 9 0 0 0 ktr wrb 9wb 7wb ht1 ht1 5 1hsl 1 3 flpktr invktr 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 axf skr 1 0
Hatchet Hands h2h axf axf axf 100 2 1 2 15 14 1 10 75 75 37 37 28 12 0 0 0 axf axf 9xf 7xf ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 ces clw 1 0
Cestus h2h ces axf ces 100 2 1 7 15 14 1 75 75 42 42 36 15 0 0 0 axf ces 9cs 7cs ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 clw btl 1 0
Claws h2h clw clw clw 100 2 1 8 15 14 1 -10 75 75 46 46 32 18 0 0 0 clw clw 9lw 7lw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 btl xxx 1 0
Blade Talons h2h btl clw btl 100 2 1 10 14 14 1 -20 75 75 50 50 35 21 0 0 0 clw btl 9tw 7tw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Scissors Katar h2h skr skr skr 100 2 1 9 17 14 1 -10 75 75 55 55 34 24 0 0 0 skr skr 9qr 7qr ht1 ht1 5 1hsl 1 3 flpskr invskr invskru invskru 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Quhab h2h 9ar ktr 9ar 100 2 1 11 24 14 1 75 75 57 57 24 28 21 0 0 ktr ktr 9ar 7ar ht1 ht1 5 1hsl 1 3 flpktr invktr 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Wrist Spike h2h 9wb ktr 9wb 100 2 1 13 27 14 1 -10 75 75 66 66 28 32 24 0 0 ktr wrb 9wb 7wb ht1 ht1 5 1hsl 1 3 flpktr invktr 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Fascia h2h 9xf axf 9xf 100 2 1 8 37 14 1 10 75 75 69 69 32 36 27 0 0 axf axf 9xf 7xf ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Hand Scythe h2h2 9cs axf 9cs 100 2 1 16 37 14 1 -10 75 75 73 73 36 41 30 0 0 axf ces 9cs 7cs ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Greater Claws h2h2 9lw clw 9lw 100 2 1 18 37 14 1 -20 75 75 76 76 26 45 33 0 0 clw clw 9lw 7lw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Greater Talons h2h2 9tw clw 9tw 100 2 1 21 35 14 1 -30 75 75 79 79 35 50 37 0 0 clw btl 9tw 7tw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Scissors Quhab h2h2 9qr skr 9qr 100 2 1 19 40 14 1 75 75 82 82 34 54 40 0 0 skr skr 9qr 7qr ht1 ht1 5 1hsl 1 3 flpskr invskr invskru invskru 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Suwayyah h2h2 7ar ktr 7ar 100 2 1 39 52 14 1 75 75 99 99 24 59 44 0 0 ktr ktr 9ar 7ar ht1 ht1 5 1hsl 1 3 flpktr invktr 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Wrist Sword h2h2 7wb ktr 7wb 100 2 1 34 45 14 1 -10 75 75 105 105 28 62 46 0 0 ktr wrb 9wb 7wb ht1 ht1 5 1hsl 1 3 flpktr invktr 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
War Fist h2h2 7xf axf 7xf 100 2 1 44 53 14 1 10 75 75 108 108 32 68 51 0 0 axf axf 9xf 7xf ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Battle Cestus h2h2 7cs axf 7cs 100 2 1 36 42 14 1 -10 75 75 110 110 36 73 54 0 0 axf ces 9cs 7cs ht1 ht1 5 1hsl 1 3 flpaxf invaxf invaxfu invaxfu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Feral Claws h2h2 7lw clw 7lw 100 2 1 22 53 14 1 -20 75 75 113 113 26 78 58 0 0 clw clw 9lw 7lw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Runic Talons h2h2 7tw clw 7tw 100 2 1 24 44 14 1 -30 75 75 115 115 35 81 60 0 0 clw btl 9tw 7tw ht1 ht1 5 1hsl 1 3 flpclw invclw 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Scissors Suwayyah h2h2 7qr skr 7qr 100 2 1 40 51 14 1 75 75 118 118 34 85 64 0 0 skr skr 9qr 7qr ht1 ht1 5 1hsl 1 3 flpskr invskr invskru invskru 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Tomahawk axe 7ha hax 7ha 100 3 1 33 58 14 100 125 67 14 54 40 0 0 hax hax 9ha 7ha 1hs 1hs 5 1hsl 1 3 flphax invhax invhaxu invhaxu 1 4 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 0 0 3 5 2 0 xxx xxx 1 0
Small Crescent axe 7ax axe 7ax 100 4 1 38 60 14 1 10 100 115 83 12 61 45 0 0 axe axe 9ax 7ax 1hs 1hs 5 1hsl 2 3 flpaxe invaxe invaxeu invaxeu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
Ettin Axe axe 72a axe 72a 100 4 1 33 66 14 1 10 100 145 45 12 70 52 0 0 axe 2ax 92a 72a 1hs 1hs 5 1hsl 2 3 flp2ax inv2ax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
War Spike axe 7mp axe 7mp 100 4 1 30 48 14 1 -10 100 133 54 13 79 59 0 0 axe mpi 9mp 7mp 1hs 1hs 5 1hsl 2 3 flpmpi invmpi invmpiu invmpiu 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Berserker Axe axe 7wa hax 7wa 100 4 1 24 71 14 2 100 138 59 13 85 64 0 0 hax wax 9wa 7wa 1hs 1hs 5 1hsl 2 3 flpwax invwax 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Feral Axe axe 7la lax 7la 100 4 1 1 25 123 17 2 -15 100 196 15 57 42 0 0 lax lax 9la 7la stf stf 5 2hsl 2 3 flplax invlax 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Silver Edged Axe axe 7ba lax 7ba 100 4 1 1 62 110 17 2 100 166 65 18 65 48 0 0 lax bax 9ba 7ba stf stf 5 2hsl 2 3 flpbrx invbrx 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Decapitator axe 7bt btx 7bt 100 4 1 1 49 137 17 2 10 100 189 33 20 73 54 0 0 btx btx 9bt 7bt stf stf 5 2hsl 2 3 flpbtx invbtx invbtxu invbtxu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 5 2 0 xxx xxx 1 0
Champion Axe axe 7ga btx 7ga 100 4 1 1 59 94 17 2 -10 100 167 59 25 82 61 0 0 btx gax 9ga 7ga stf stf 5 2hsl 2 4 flpgax invgax invgaxu invgaxu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Glorious Axe axe 7gi gix 7gi 100 4 1 1 60 124 17 3 10 100 164 55 25 85 66 0 0 gix gix 9gi 7gi stf stf 5 2hsl 2 3 flpgix invgix 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Polished Wand wand 7wn wnd 7wn 100 1 1 18 33 14 100 25 11 55 41 0 0 wnd wnd 9wn 7wn 1hs 1hs 5 1hss 1 2 flpwnd invwnd 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Ghost Wand wand 7yw ywn 7yw 100 1 1 20 40 14 10 100 25 7 65 48 0 0 ywn ywn 9yw 7yw 1hs 1hs 5 1hss 1 2 flpywn invywn 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Lich Wand wand 7bw bwn 7bw 100 1 1 10 31 14 -20 100 25 9 75 56 0 0 bwn bwn 9bw 7bw 1hs 1hs 5 1hss 1 2 flpbwn invbwn invbwnu invbwnu 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Unearthed Wand wand 7gw bwn 7gw 100 1 1 22 28 14 100 25 9 86 64 0 0 bwn gwn 9gw 7gw 1hs 1hs 5 1hss 1 2 flpgwn invgwn 1 4 0 magically charged 0 item_wand 12 item_wand 0 0 5 0 0 0 0 7 0 0 5 1 2 0 xxx xxx 1 0
Truncheon club 7cl clb 7cl 100 1 1 35 43 14 -10 100 88 43 28 52 39 0 0 clb clb 9cl 7cl 1hs 1hs 5 club 1 3 flpclb invclb invclbu invclbu 1 4 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 5 2 0 xxx xxx 1 0
Mighty Scepter scep 7sc mac 7sc 100 2 1 40 52 14 100 125 65 25 62 46 0 0 mac scp 9sc 7sc 1hs 1hs 5 club 1 3 flpscp invscp 1 4 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Seraph Rod scep 7qs mac 7qs 100 4 1 45 54 14 1 10 100 108 69 30 76 57 0 0 mac gsc 9qs 7qs 1hs 1hs 5 club 1 3 flpgsc invgsc 1 5 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Caduceus scep 7ws whm 7ws 100 4 1 37 43 14 1 -10 100 97 70 35 85 66 0 0 whm wsp 9ws 7ws 1hs 1hs 5 club 2 3 flpwsp invwsp 1 7 0 magically charged 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Tyrant Club club 7sp clb 7sp 100 3 1 32 58 14 1 100 133 33 57 42 0 0 clb spc 9sp 7sp 1hs 1hs 5 1hsl 1 3 flpspc invspc invspcu invspcu 1 5 0 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 1 2 2 0 xxx xxx 1 0
Reinforced Mace mace 7ma mac 7ma 100 4 1 41 49 14 100 145 46 30 63 47 0 0 mac mac 9ma 7ma 1hs 1hs 5 1hsl 1 3 flpmac invmac inv7mas 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Devil Star mace 7mt mac 7mt 100 4 1 43 53 14 1 10 100 153 44 36 70 52 0 0 mac mst 9mt 7mt 1hs 1hs 5 1hsl 1 3 flpmst invmst invmstu invmstu 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Scourge mace 7fl fla 7fl 100 4 1 3 80 14 2 -10 100 125 77 33 76 57 0 0 fla fla 9fl 7fl 1hs 1hs 5 1hsl 2 3 flpfla invfla 1 7 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Legendary Mallet hamm 7wh whm 7wh 100 4 1 50 61 14 1 20 110 189 33 82 61 0 0 whm whm 9wh 7wh 1hs 1hs 5 1hsl 2 3 flpwhm invwhm 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 178 7 0 0 3 2 2 0 xxx xxx 1 0
Ogre Maul hamm 7m7 mau 7m7 100 4 1 1 77 106 17 1 10 110 225 30 69 51 0 0 mau mau 9m9 7m7 stf stf 5 2hsl 2 4 flpmau invmau 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Thunder Maul hamm 7gm mau 7gm 100 3 1 1 33 180 17 2 20 110 253 30 85 65 0 0 mau gma 9gm 7gm stf stf 5 2hsl 2 3 flpgma invgma 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Falcata swor 7ss ssd 7ss 100 3 1 31 59 14 100 150 88 12 56 42 0 0 ssd ssd 9ss 7ss 1hs 1hs 5 1hsl 1 3 flpssd invssd 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Ataghan swor 7sm scm 7sm 100 3 1 26 46 14 -20 100 138 95 11 61 45 0 0 scm scm 9sm 7sm 1hs 1hs 5 1hsl 1 3 flpscm invscm invscmu invscmu 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Elegant Blade swor 7sb scm 7sb 100 3 1 33 45 14 -10 100 109 122 16 63 47 0 0 scm sbr 9sb 7sb 1hs 1hs 5 1hsl 1 3 flpsbr invsbr invsbru invsbru 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Hydra Edge swor 7fc flc 7fc 100 4 1 28 68 14 10 100 142 105 16 69 51 0 0 flc flc 9fc 7fc 1hs 1hs 5 1hsl 1 3 flpflc invflc 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Phase Blade swor 7cr crs 7cr 100 2 1 31 35 14 1 -30 100 25 136 0 1 73 54 0 0 crs crs 9cr 7cr 1hs 1hs 5 1hsl 2 3 flpcrs invcrs invcrsu invcrsu 1 7 0 0 item_sword 12 item_sword 0 0 5 0 5 0 0 7 0 0 1 1 8 0 xxx xxx 1 0
Conquest Sword swor 7bs bsd 7bs 100 4 1 37 53 14 100 142 112 16 78 58 0 0 bsd bsd 9bs 7bs 1hs 1hs 5 1hsl 2 3 flpbsd invbsd 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Cryptic Sword swor 7ls lsd 7ls 100 4 1 5 77 14 1 -10 100 99 109 22 82 61 0 0 lsd lsd 9ls 7ls 1hs 1hs 5 1hsl 2 3 flplsd invlsd invlsdu invlsdu 1 6 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Mythical Sword swor 7wd flc 7wd 100 4 1 40 50 14 1 100 147 124 22 85 66 0 0 flc wsd 9wd 7wd 1hs 1hs 5 1hsl 1 3 flpwsd invwsd 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Legend Sword swor 72h clm 72h 100 4 1 22 56 1 1 50 94 17 2 -15 100 175 100 22 59 44 0 0 clm 2hs 92h 72h 1hs 2hs 5 2hss 1 4 flp2hs inv2hs inv2hsu inv2hsu 1 5 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Highland Blade swor 7cm clm 7cm 100 4 1 22 62 1 1 67 96 17 2 -5 100 171 104 25 66 49 0 0 clm clm 9cm 7cm 1hs 2hs 5 2hss 1 4 flpclm invclm 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Balrog Blade swor 7gs gsd 7gs 100 4 1 15 75 1 1 55 118 17 2 100 185 87 25 71 53 0 0 gsd gis 9gs 7gs 1hs 2hs 5 2hss 1 4 flpgis invgis invgisu invgisu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Champion Sword swor 7b7 clm 7b7 100 4 1 24 54 1 1 71 83 17 2 -10 100 163 103 20 77 57 0 0 clm bsw 9b9 7b7 1hs 2hs 5 2hss 1 4 flpbsw invbsw invbswu invbswu 1 6 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Colossal Sword swor 7fb clm 7fb 100 4 1 26 70 1 1 61 121 17 2 10 100 182 95 25 80 60 0 0 clm flb 9fb 7fb 1hs 2hs 5 2hss 2 4 flpflb invflb 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Colossus Blade swor 7gd gsd 7gd 100 4 1 25 65 1 1 58 115 17 2 5 100 189 110 25 85 63 0 0 gsd gsd 9gd 7gd 1hs 2hs 5 2hss 2 4 flpgsd invgsd invgsdu invgsdu 1 7 0 0 item_largemetalweapon 12 item_largemetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Bone Knife knif 7dg dgr 7dg 100 2 1 23 49 14 -20 75 75 38 75 13 58 43 0 0 dgr dgr 9dg 7dg 1ht 1ht 5 1ht 1 2 flpdgr invdgr 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Mithral Point knif 7di dir 7di 100 2 1 37 53 14 75 75 55 98 28 70 52 0 0 dir dir 9di 7di 1ht 1ht 5 1ht 1 2 flpdir invdir 1 3 0 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Fanged Knife knif 7kr dir 7kr 100 2 1 15 57 14 -20 75 75 42 86 18 83 62 0 0 dir kri 9kr 7kr 1ht 1ht 5 1ht 1 3 flpkrs invkrs invkrsu invkrsu 1 5 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Legend Spike knif 7bl dgr 7bl 100 3 1 31 47 14 -10 75 75 65 67 24 85 66 0 0 dgr bld 9bl 7bl 1ht 1ht 5 1ht 1 3 flpbld invbld 1 4 0 0 item_sword 12 item_sword 0 0 5 0 0 0 0 7 0 0 3 1 2 0 xxx xxx 1 0
Flying Knife tkni 7tk dgr 7tk 100 2 1 23 54 23 54 15 75 75 48 141 12 64 48 0 0 dgr tkf 9tk 7tk 1ht 1ht 5 1ht 1 2 1 40 200 75 flptkn invtkn 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 xxx xxx 1 0
Flying Axe taxe 7ta hax 7ta 100 2 1 17 65 15 66 15 10 75 75 88 108 16 56 42 0 0 hax tax 9ta 7ta 1hs 1hs 5 1hsl 1 2 1 16 180 32 flptax invtax 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 35 7 5 0 3 5 2 0 xxx xxx 1 0
Winged Knife tkni 7bk dgr 7bk 100 2 1 27 35 23 39 15 -20 75 75 45 142 10 77 57 0 0 dgr bkf 9bk 7bk 1ht 1ht 5 1ht 1 2 1 30 200 60 flpbkf invbkf invtk3 1 4 0 primarily thrown 0 item_smallmetalweapon 12 item_smallmetalweapon 0 0 5 0 0 0 36 7 4 0 3 1 2 0 xxx xxx 1 0
Winged Axe taxe 7b8 hax 7b8 100 2 1 11 56 7 60 15 -10 75 75 96 122 16 80 60 0 0 hax bal 9b8 7b8 1hs 1hs 5 1hsl 2 3 1 16 180 24 flpbal invbal 1 4 0 primarily thrown 0 item_sword 12 item_sword 0 0 5 0 0 0 35 7 5 0 3 2 2 0 xxx xxx 1 0
Hyperion Javelin jave 7ja jav 7ja 100 4 1 21 57 28 55 14 2 -10 75 75 98 123 10 54 40 0 0 jav jav 9ja 7ja 1ht 1ht 5 1ht 1 3 1 30 100 60 flpjav invjav 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 1 7 2 0 3 5 8 0 xxx xxx 1 0
Stygian Pilum jave 7pi pil 7pi 100 4 1 14 64 21 75 14 2 75 75 118 112 12 62 46 0 0 pil pil 9pi 7pi 1ht 1ht 5 1ht 1 3 1 25 90 50 flppil invpil 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 5 8 0 xxx xxx 1 0
Balrog Spear jave 7s7 jav 7s7 100 4 1 33 63 40 62 14 2 10 75 75 127 95 14 71 53 0 0 jav ssp 9s9 7s7 1ht 1ht 5 1ht 1 3 1 20 80 40 flpssp invssp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 5 8 0 xxx xxx 1 0
Ghost Glaive jave 7gl glv 7gl 100 4 1 19 60 30 85 14 2 20 75 75 89 137 16 79 59 0 0 glv glv 9gl 7gl 1ht 1ht 5 1ht 1 4 1 8 75 20 flpglv invglv 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 1 2 0 xxx xxx 1 0
Winged Harpoon jave 7ts pil 7ts 100 4 1 27 35 11 77 14 2 -10 75 75 76 145 18 85 65 0 0 pil tsp 9ts 7ts 1ht 1ht 5 1ht 1 4 1 32 80 80 flptsp invtsp 1 4 0 primarily thrown 0 item_javelins 12 item_javelins 0 0 5 0 0 0 37 7 2 0 3 1 2 0 xxx xxx 1 0
Hyperion Spear spea 7sr spr 7sr 100 4 1 1 35 119 17 3 -10 100 155 120 15 58 43 0 0 spr spr 9sr 7sr 2ht 2ht 5 2ht 2 4 flpspr invspr 1 5 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Stygian Pike spea 7tr tri 7tr 100 4 1 1 29 144 17 3 100 168 97 18 66 49 0 0 tri tri 9tr 7tr 2ht 2ht 5 2ht 2 4 flptri invtri invtriu invtriu 1 6 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Mancatcher spea 7br brn 7br 100 4 1 1 42 92 17 4 -20 100 132 134 14 74 55 0 0 brn brn 9br 7br 2ht 2ht 5 2ht 2 4 flpbrn invbrn 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Ghost Spear spea 7st tri 7st 100 4 1 1 18 155 17 4 100 122 163 14 83 62 0 0 tri spt 9st 7st 2ht 2ht 5 2ht 2 4 flpspt invspt 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
War Pike spea 7p7 pik 7p7 100 4 1 1 33 178 17 4 20 100 165 106 13 85 66 0 0 pik pik 9p9 7p7 2ht 2ht 5 2ht 2 4 flppik invpik 1 7 0 3 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Ogre Axe pole 7o7 hal 7o7 100 4 1 1 28 145 17 2 100 195 75 25 60 45 0 0 hal bar 9b7 7o7 stf stf 5 2hsl 2 4 flpbar invbar 1 5 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Colossus Voulge pole 7vo hal 7vo 100 4 1 1 17 165 17 2 10 100 210 55 25 64 48 0 0 pax vou 9vo 7vo stf stf 5 2hsl 2 4 flpvou invvou 1 6 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Thresher pole 7s8 scy 7s8 100 4 1 1 12 141 17 1 -10 100 152 118 33 71 53 0 0 scy scy 9s8 7s8 stf stf 5 2hsl 2 4 flpscy invscy invscyu invscyu 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Cryptic Axe pole 7pa hal 7pa 100 4 1 1 33 150 17 3 10 100 165 103 33 79 59 0 0 pax pax 9pa 7pa stf stf 5 2hsl 2 4 flppax invpax 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Great Poleaxe pole 7h7 pax 7h7 100 4 1 1 46 127 17 4 100 179 99 28 84 63 0 0 hal hal 9h9 7h7 stf stf 5 2hsl 2 4 flphal invhal 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Giant Thresher pole 7wc scy 7wc 100 3 1 1 40 114 17 4 -10 100 188 140 28 85 66 0 0 scy wsc 9wc 7wc stf stf 5 2hsl 2 4 flpwsc invwsc 1 7 0 2 square reach 0 item_woodweaponlarge 12 item_woodweaponlarge 0 0 5 0 0 0 0 7 0 0 3 2 2 0 xxx xxx 1 0
Walking Stick staf 6ss bst 6ss 100 2 1 1 69 85 17 1 -10 100 25 10 58 43 0 0 1 bst sst 8ss 6ss stf stf 5 club 1 3 flpsst invsst 1 4 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Stalagmite staf 6ls sst 6ls 100 2 1 1 75 107 17 1 10 100 63 35 15 66 49 0 0 1 sst lst 8ls 6ls stf stf 5 staf 1 4 flplst invlst 1 5 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Elder Staff staf 6cs cst 6cs 100 2 1 1 80 93 17 1 100 44 37 18 74 55 0 0 1 cst cst 8cs 6cs stf stf 5 staf 1 4 flpcst invcst invcstu invcstu 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Shillelah staf 6bs sst 6bs 100 2 1 1 65 108 17 1 100 52 27 20 83 62 0 0 1 sst bst 8bs 6bs stf stf 5 staf 1 4 flpbst invbst 1 6 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Archon Staff staf 6ws lst 6ws 100 2 1 1 83 99 17 1 10 100 34 13 85 66 0 0 1 lst wst 8ws 6ws stf stf 5 staf 2 4 flpwst invwst 1 7 0 magically charged 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Spider Bow bow 6sb sbw 6sb 100 2 1 1 23 50 13 5 100 64 143 10 1 55 41 0 0 sbw sbw 8sb 6sb bow bow 6 bow 2 3 flpsbw invsbw 1 5 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Blade Bow bow 6hb sbw 6hb 100 2 1 1 21 41 13 -10 100 76 119 16 1 60 45 0 0 sbw hbw 8hb 6hb bow bow 6 bow 2 3 flphbw invhbw 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Shadow Bow bow 6lb lbw 6lb 100 2 1 1 15 59 13 100 52 188 14 1 63 47 0 0 lbw lbw 8lb 6lb bow bow 6 bow 2 4 flplbw invlbw 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Great Bow bow 6cb lbw 6cb 100 2 1 1 12 52 13 -10 100 121 107 18 1 68 51 0 0 lbw cbw 8cb 6cb bow bow 6 bow 2 3 flpcbw invcbw invcbwu invcbwu 1 6 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Diamond Bow bow 6s7 sbb 6s7 100 2 1 1 33 40 13 100 89 132 20 1 72 54 0 0 sbb sbb 8s8 6s7 bow bow 6 bow 2 3 flpsbb invsbb invsbbu invsbbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Crusader Bow bow 6l7 lbb 6l7 100 2 1 1 15 63 13 10 100 97 121 22 1 77 57 0 0 lbb lbb 8l8 6l7 bow bow 6 bow 2 4 flplbb invlbb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Ward Bow bow 6sw sbb 6sw 100 2 1 1 20 53 13 100 72 146 24 1 80 60 0 0 sbb swb 8sw 6sw bow bow 6 bow 2 3 flpswb invswb invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Hydra Bow bow 6lw lbb 6lw 100 2 1 1 10 68 13 10 100 134 167 28 1 85 63 0 0 lbb lwb 8lw 6lw bow bow 6 bow 2 4 flplwb invlwb 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Pellet Bow xbow 6lx lxb 6lx 100 2 1 1 28 73 19 -10 100 83 155 15 1 57 42 0 0 lxb lxb 8lx 6lx xbw xbw 5 xbow 2 3 flplxb invlxb invlxbu invlxbu 1 5 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Gorgon Crossbow xbow 6mx lxb 6mx 100 2 1 1 25 87 19 100 117 105 20 1 67 50 0 0 lxb mxb 8mx 6mx xbw xbw 5 xbow 2 3 flpmxb invmxb invmxbu invmxbu 1 6 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Colossus Crossbow xbow 6hx hxb 6hx 100 2 1 1 32 91 19 10 100 163 77 25 1 75 56 0 0 hxb hxb 8hx 6hx xbw xbw 5 xbow 2 4 flphxb invhxb invhxbu invhxbu 1 7 0 reload lag between shots 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Demon Crossbow xbow 6rx hxb 6rx 100 2 1 1 26 40 19 -60 100 141 98 20 1 84 63 0 0 hxb rxb 8rx 6rx xbw xbw 5 xbow 2 3 flprxb invrxb invrxbu invrxbu 1 7 0 fires 5 shots before reload 0 item_crossbow 12 item_crossbow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Eagle Orb orb ob1 ob1 ob1 100 2 1 2 5 14 -10 100 10 1 0 0 0 1 303 bst ob1 ob6 obb 1hs 1hs 5 1hss 1 2 flpob1 invob1 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Sacred Globe orb ob2 ob1 ob2 100 3 1 3 8 14 -10 100 15 8 0 0 0 1 303 bst ob2 ob7 obc 1hs 1hs 5 1hss 1 2 flpob1 invob2 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Smoked Sphere orb ob3 ob3 ob3 100 3 1 4 10 14 100 18 12 8 0 0 1 303 bst ob3 ob8 obd 1hs 1hs 5 1hss 1 2 flpob3 invob3 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Clasped Orb orb ob4 ob4 ob4 100 4 1 5 12 14 100 20 17 13 0 0 1 303 bst ob4 ob9 obe 1hs 1hs 5 1hss 1 2 flpob4 invob4 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Dragon Stone orb ob5 ob4 ob5 100 5 1 8 18 14 10 100 25 24 18 0 0 1 303 bst ob5 oba obf 1hs 1hs 5 1hss 1 3 flpob5 invob5 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Stag Bow abow am1 am1 am1 100 3 1 1 7 12 13 100 30 45 24 1 18 14 0 0 300 sbw am1 am6 amb bow bow 6 bow 2 4 flpam1 invam1 invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Reflex Bow abow am2 am2 am2 100 3 1 1 9 19 13 10 100 35 60 28 1 27 20 0 0 300 sbw am2 am7 amc bow bow 6 bow 2 4 flpam2 invam2 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Maiden Spear aspe am3 am3 am3 100 3 1 1 18 24 15 4 80 50 54 40 14 18 14 0 0 302 spr am3 am8 amd 2ht 2ht 5 2ht 2 4 flpam3 invam3 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Maiden Pike aspe am4 pik am4 100 3 1 1 23 55 15 4 10 80 50 63 52 13 27 20 0 0 302 spr am4 am9 ame 2ht 2ht 5 2ht 2 4 flppik invam4 1 7 0 3 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Maiden Javelin ajav am5 pil am5 100 3 1 8 14 6 22 15 2 -10 80 50 33 47 15 23 17 0 0 302 jav am5 ama amf 1ht 1ht 5 1ht 1 3 1 32 80 80 flpam5 invam5 1 4 0 primarily thrown 0 item_staff 12 item_staff 0 0 5 0 0 0 371 7 2 0 3 1 2 0 xxx xxx 1 0
Glowing Orb orb ob6 ob1 ob6 100 2 1 8 21 14 -10 100 10 32 24 0 0 1 303 bst ob1 ob6 obb 1hs 1hs 5 1hss 1 2 flpob1 invob1 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Crystalline Globe orb ob7 ob1 ob7 100 3 1 10 26 14 -10 100 15 37 27 0 0 1 303 bst ob2 ob7 obc 1hs 1hs 5 1hss 1 2 flpob1 invob2 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Cloudy Sphere orb ob8 ob3 ob8 100 3 1 11 29 14 100 18 41 30 0 0 1 303 bst ob3 ob8 obd 1hs 1hs 5 1hss 1 2 flpob3 invob3 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Sparkling Ball orb ob9 ob4 ob9 100 4 1 13 32 14 100 20 46 34 0 0 1 303 bst ob4 ob9 obe 1hs 1hs 5 1hss 1 2 flpob4 invob4 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Swirling Crystal orb oba ob4 oba 100 5 1 18 42 14 10 100 25 50 37 0 0 1 303 bst ob5 oba obf 1hs 1hs 5 1hss 1 3 flpob5 invob5 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Ashwood Bow abow am6 am1 am6 100 3 1 1 16 29 13 100 56 77 24 1 39 29 0 0 300 sbw am1 am6 amb bow bow 6 bow 2 4 flpam1 invam1 invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Ceremonial Bow abow am7 am2 am7 100 3 1 1 19 41 13 10 100 73 110 28 1 47 35 0 0 300 sbw am2 am7 amc bow bow 6 bow 2 4 flpam2 invam2 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Ceremonial Spear aspe am8 am3 am8 100 3 1 1 34 51 15 4 80 50 101 80 14 43 32 0 0 302 spr am3 am8 amd 2ht 2ht 5 2ht 2 4 flpam3 invam3 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Ceremonial Pike aspe am9 pik am9 100 3 1 1 42 101 15 4 20 80 50 115 98 13 51 38 0 0 302 spr am4 am9 ame 2ht 2ht 5 2ht 2 4 flppik invam4 1 7 0 3 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Ceremonial Javelin ajav ama pil ama 100 3 1 18 35 18 54 15 2 -10 80 50 25 109 15 35 26 0 0 302 jav am5 ama amf 1ht 1ht 5 1ht 1 3 1 32 80 80 flpam5 invam5 1 4 0 primarily thrown 0 item_staff 12 item_staff 0 0 5 0 0 0 371 7 2 0 3 1 2 0 xxx xxx 1 0
Heavenly Stone orb obb ob1 obb 100 2 1 21 46 14 -10 100 10 59 44 0 0 1 303 bst ob1 ob6 obb 1hs 1hs 5 1hss 1 2 flpob1 invob1 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Eldritch Orb orb obc ob1 obc 100 3 1 18 50 14 -10 100 15 67 50 0 0 1 303 bst ob2 ob7 obc 1hs 1hs 5 1hss 1 2 flpob1 invob2 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Demon Heart orb obd ob3 obd 100 3 1 23 55 14 100 18 75 56 0 0 1 303 bst ob3 ob8 obd 1hs 1hs 5 1hss 1 2 flpob3 invob3 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Vortex Orb orb obe ob4 obe 100 4 1 12 66 14 100 20 84 63 0 0 1 303 bst ob4 ob9 obe 1hs 1hs 5 1hss 1 2 flpob4 invob4 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Dimensional Shard orb obf ob4 obf 100 5 1 30 53 14 10 100 25 85 66 0 0 1 303 bst ob5 oba obf 1hs 1hs 5 1hss 1 3 flpob5 invob5 1 6 0 0 item_orb 12 item_orb 0 0 5 0 1 0 0 7 0 0 5 5 8 0 xxx xxx 1 0
Matriarchal Bow abow amb am1 amb 100 3 1 1 20 47 13 -10 100 87 187 24 1 53 39 0 0 300 sbw am1 am6 amb bow bow 6 bow 2 4 flpam1 invam1 invswbu invswbu 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Grand Matron Bow abow amc am2 amc 100 3 1 1 14 72 13 10 100 108 152 28 1 78 58 0 0 300 sbw am2 am7 amc bow bow 6 bow 2 4 flpam2 invam2 1 7 0 0 item_bow 12 item_bow 0 0 5 1 0 0 0 7 0 0 1 5 8 0 xxx xxx 1 0
Matriarchal Spear aspe amd am3 amd 100 3 1 1 65 95 15 4 80 50 114 142 14 61 45 0 0 302 spr am3 am8 amd 2ht 2ht 5 2ht 2 4 flpam3 invam3 1 7 0 2 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
Matriarchal Pike aspe ame pik ame 100 3 1 1 37 153 15 4 20 80 50 132 149 13 81 60 0 0 302 spr am4 am9 ame 2ht 2ht 5 2ht 2 4 flppik invam4 1 7 0 3 square reach 0 item_staff 12 item_staff 0 0 5 0 0 0 0 7 0 0 3 5 8 0 xxx xxx 1 0
MatriarchalJavelin ajav amf pil amf 100 3 1 30 54 35 66 15 2 -10 80 50 107 151 20 65 48 0 0 302 jav am5 ama amf 1ht 1ht 5 1ht 1 3 1 32 80 80 flpam5 invam5 1 4 0 primarily thrown 0 item_staff 12 item_staff 0 0 5 0 0 0 371 7 2 0 3 1 2 0 xxx xxx 1 0

5
composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"gabordemooij/redbean": "^5.6"
}
}

60
composer.lock generated Normal file
View File

@ -0,0 +1,60 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9ea61108070b0206355280587ef0593b",
"packages": [
{
"name": "gabordemooij/redbean",
"version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/gabordemooij/redbean.git",
"reference": "fac13cd25445129a64be2bbb4457a7d8a4fb9ef0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/fac13cd25445129a64be2bbb4457a7d8a4fb9ef0",
"reference": "fac13cd25445129a64be2bbb4457a7d8a4fb9ef0",
"shasum": ""
},
"require": {
"php": ">=5.3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"RedBeanPHP\\": "RedBeanPHP"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Gabor de Mooij",
"email": "gabor@redbeanphp.com",
"homepage": "https://redbeanphp.com"
}
],
"description": "RedBeanPHP ORM",
"homepage": "https://redbeanphp.com/",
"keywords": [
"orm"
],
"time": "2020-11-27T08:36:10+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "1.1.0"
}

4
config.php Normal file
View File

@ -0,0 +1,4 @@
<?php
//$d2_dir = 'D:\\Diablo II\\MODS\\ironman-dev\\';
$d2_dir = '/home/stoned/code/d2/';

View File

@ -1,11 +1,35 @@
<?php
ini_set('display_errors', 0);
ini_set('log_errors', 1);
require_once 'vendor/autolaod.php';
require_once "config.php";
//$path = $d2_dir."data\\global\\excel\\";
$path = $d2_dir."data/global/excel/";
$post = $_POST;
if (!empty($code)) { array_filter($_POST['code']);}
$post['code'] = $code[0];
if (!empty($post['code'])) {
array_filter($post['code']);
if(!empty($post['code'][0])) {
$post['code'] = $post['code'][0];
} else {
$post['code'] = $post['code'][1];
}
}
require_once 'uniqueitems.php';
$fp = fopen($u, 'a+');
fputcsv($fp, $post, "\t");
//echo "<pre>";
//print_r($post);
//echo "</pre>";
if (!empty($_POST)) {fputcsv($fp, $post, "\t");}

View File

@ -43,10 +43,17 @@ input {
<h1>Unique Maker by Hash</h1>
<body>
<?php
$u = "UniqueItems.txt";
$w = "Weapons.txt";
$a = "Armor.txt";
$p = "Properties.txt";
$u = $path.$u;
$w = $path.$w;
$a = $path.$a;
$p = $path.$p;
function toPHP($file)
{
$rows = array_map(function ($v)
@ -65,13 +72,24 @@ $armor = toPHP($a);
$weapon = toPHP($w);
$prop = toPHP($p);
$uni = toPHP($u);
// echo '<pre>';
// print_r($uni);
// die();
?>
<form action="/index.php" method="post">
<div class="container">
<div class="inputs-top">
Index: <input style="width: 300px;" type="text" name="index" ><br>
Version: <input type="number" name="version" value="100">
Enabled: <input type="boolean" name="enabled" value="1">
<br>
<input type="radio" id="enabled" name="enabled" value="1">
<label for="enabled">Enabled</label>
<input type="radio" id="disabled" name="enabled" value="0">
<label for="disabled">Disabled</label>
<br>
<input type="hidden" name="ladder" >
Rarity: <input type="number" name="rarity" >
<input type="hidden" name="nolimit" value="">
@ -116,7 +134,7 @@ Usesound: <input type="text" name="usesound" value="">
</div>
<?php
echo "<div class='container'><div class='row'>";
$html = '';
foreach (range(01, 12) as $p)
{
$html .= '<div style="border: 1px solid black;margin: 5px;padding: 15px;" class="col-2"><h4>Prop ' . $p . '</h4>';
@ -135,7 +153,7 @@ foreach (range(01, 12) as $p)
echo $html;
?>
<div>
Usesound: <input type="hidden" name="eol" value="0"> <br>;
<input type="hidden" name="eol" value="0"> <br>
<button class='btn-success' style="height: 48px;width: 250px;" id="submit">Save Item</button>
<input class="btn-danger" style="height: 48px;width:200px; margin-left: 10px;" type="reset">
</div>

7
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit7881a223d5c4e5800bc6cdeb88fd0b5a::getLoader();

445
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

19
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

9
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

10
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'RedBeanPHP\\' => array($vendorDir . '/gabordemooij/redbean/RedBeanPHP'),
);

55
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,55 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit7881a223d5c4e5800bc6cdeb88fd0b5a
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit7881a223d5c4e5800bc6cdeb88fd0b5a', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit7881a223d5c4e5800bc6cdeb88fd0b5a', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit7881a223d5c4e5800bc6cdeb88fd0b5a::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

31
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,31 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit7881a223d5c4e5800bc6cdeb88fd0b5a
{
public static $prefixLengthsPsr4 = array (
'R' =>
array (
'RedBeanPHP\\' => 11,
),
);
public static $prefixDirsPsr4 = array (
'RedBeanPHP\\' =>
array (
0 => __DIR__ . '/..' . '/gabordemooij/redbean/RedBeanPHP',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit7881a223d5c4e5800bc6cdeb88fd0b5a::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit7881a223d5c4e5800bc6cdeb88fd0b5a::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

45
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,45 @@
[
{
"name": "gabordemooij/redbean",
"version": "v5.6.2",
"version_normalized": "5.6.2.0",
"source": {
"type": "git",
"url": "https://github.com/gabordemooij/redbean.git",
"reference": "fac13cd25445129a64be2bbb4457a7d8a4fb9ef0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/fac13cd25445129a64be2bbb4457a7d8a4fb9ef0",
"reference": "fac13cd25445129a64be2bbb4457a7d8a4fb9ef0",
"shasum": ""
},
"require": {
"php": ">=5.3.4"
},
"time": "2020-11-27T08:36:10+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"RedBeanPHP\\": "RedBeanPHP"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Gabor de Mooij",
"email": "gabor@redbeanphp.com",
"homepage": "https://redbeanphp.com"
}
],
"description": "RedBeanPHP ORM",
"homepage": "https://redbeanphp.com/",
"keywords": [
"orm"
]
}
]

29
vendor/gabordemooij/redbean/.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
*.pyc
# Logs and databases #
######################
*.log
# OS generated files #
######################
.DS_Store*
ehthumbs.db
Icon?
Thumbs.db
/.project
/.settings/org.eclipse.php.core.prefs
/.settings/org.eclipse.php.debug.core.Debug_Process_Preferences.prefs
/rb.phar
/rb.php
/rb-mysql.php
/rb-postgres.php
/rb-sqlite.php
build/

38
vendor/gabordemooij/redbean/.travis.yml vendored Normal file
View File

@ -0,0 +1,38 @@
language: php
matrix:
include:
- php: 5.3
dist: precise
- php: 5.4
dist: trusty
- php: 5.5
dist: trusty
- php: 5.6
dist: trusty
- php: 7.0
dist: trusty
- php: 7.1
dist: trusty
- php: 7.2
dist: trusty
- php: 7.3
dist: trusty
- php: 7.4
dist: trusty
- php: 8.0
dist: trusty
- php: nightly
dist: trusty
before_script:
- touch /tmp/oodb.db
- mysql -e 'create database oodb;'
- psql template1 -c 'CREATE EXTENSION "uuid-ossp";' -U postgres
- psql -c 'create database oodb;' -U postgres
- php replica2.php onlyphp
- cp rb.php testing/cli/testcontainer/rb.php
- cd testing/cli
script: php runtests.php

View File

@ -0,0 +1,77 @@
RedBeanPHP 5
============
[![Build Status](https://travis-ci.org/gabordemooij/redbean.svg?branch=master)](https://travis-ci.org/gabordemooij/redbean)
RedBeanPHP is an easy to use ORM tool for PHP.
* Automatically creates tables and columns as you go
* No configuration, just fire and forget
* No complicated package tools, no autoloaders, just ONE file
Installation (recommended)
---------------------------
Download RedBeanPHP from the website:
https://redbeanphp.com/download
Extract the archive and put it in your PHP project, voila!
Optional: sha256sum and check signature.
Installation via Composer (not recommended)
-----------------------------------------
Just open your composer.json file and add the package name ```(e.g. "gabordemooij/redbean": "dev-master")``` in your require list.
```json
{
"require": {
"gabordemooij/redbean": "dev-master"
}
}
```
**NOTE**:
You will find many examples on the RedBean website make use of RedBean's `R` class. Because of namespaced autoloading in Composer, this class will be available as `\RedbeanPHP\R` instead of `R`. If you desire to use the much shorter `R` alias, you can add a `use` statement at the beginning of your code:
```php
use \RedBeanPHP\R as R;
```
**NOTE:**
It is important to note that when using RedBeanPHP with Composer, there are some extra precautions needed when working with [Models](https://redbeanphp.com/index.php?p=/models). Due to the namespace requirements of Composer, when creating Models we need to use the `SimpleModel` to extend, not `RedBean_SimpleModel`. Furthermore, we need to specify the namespace of the `SimpleModel`, so a full example of using a Model with RedBean with Composer is as follows:
```php
use \RedBeanPHP\R;
class Model_User extends \RedBeanPHP\SimpleModel
{
...
}
```
Notice that we also need to add the `use \RedBeanPHP\R` statement so that we can use the `R::` shortcut within the Model.
Quick Example
-------------
How we store a book object with RedBeanPHP:
```php
$book = R::dispense("book");
$book->author = "Santa Claus";
$book->title = "Secrets of Christmas";
$id = R::store( $book );
```
Yep, it's that simple.
More information
----------------
For more information about RedBeanPHP please consult
the RedBeanPHP website:
https://www.redbeanphp.com/

View File

@ -0,0 +1,207 @@
<?php
namespace RedBeanPHP;
/**
* Adapter Interface.
* Describes the API for a RedBeanPHP Database Adapter.
* This interface defines the API contract for
* a RedBeanPHP Database Adapter.
*
* @file RedBeanPHP/Adapter.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Adapter
{
/**
* Should returns a string containing the most recent SQL query
* that has been processed by the adapter.
*
* @return string
*/
public function getSQL();
/**
* Executes an SQL Statement using an array of values to bind
* If $noevent is TRUE then this function will not signal its
* observers to notify about the SQL execution; this to prevent
* infinite recursion when using observers.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
* @param boolean $noevent no event firing
*
* @return void
*/
public function exec( $sql, $bindings = array(), $noevent = FALSE );
/**
* Executes an SQL Query and returns a resultset.
* This method returns a multi dimensional resultset similar to getAll
* The values array can be used to bind values to the place holders in the
* SQL query.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return array
*/
public function get( $sql, $bindings = array() );
/**
* Executes an SQL Query and returns a resultset.
* This method returns a single row (one array) resultset.
* The values array can be used to bind values to the place holders in the
* SQL query.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return array
*/
public function getRow( $sql, $bindings = array() );
/**
* Executes an SQL Query and returns a resultset.
* This method returns a single column (one array) resultset.
* The values array can be used to bind values to the place holders in the
* SQL query.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return array
*/
public function getCol( $sql, $bindings = array() );
/**
* Executes an SQL Query and returns a resultset.
* This method returns a single cell, a scalar value as the resultset.
* The values array can be used to bind values to the place holders in the
* SQL query.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return string
*/
public function getCell( $sql, $bindings = array() );
/**
* Executes the SQL query specified in $sql and indexes
* the row by the first column.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return array
*/
public function getAssoc( $sql, $bindings = array() );
/**
* Executes the SQL query specified in $sql and returns
* an associative array where the column names are the keys.
*
* @param string $sql Sstring containing SQL code for databaseQL
* @param array $bindings values to bind
*
* @return array
*/
public function getAssocRow( $sql, $bindings = array() );
/**
* Returns the latest insert ID.
*
* @return integer
*/
public function getInsertID();
/**
* Returns the number of rows that have been
* affected by the last update statement.
*
* @return integer
*/
public function getAffectedRows();
/**
* Returns a database agnostic Cursor object.
*
* @param string $sql string containing SQL code for database
* @param array $bindings array of values to bind to parameters in query string
*
* @return Cursor
*/
public function getCursor( $sql, $bindings = array() );
/**
* Returns the original database resource. This is useful if you want to
* perform operations on the driver directly instead of working with the
* adapter. RedBean will only access the adapter and never to talk
* directly to the driver though.
*
* @return mixed
*/
public function getDatabase();
/**
* This method is part of the RedBean Transaction Management
* mechanisms.
* Starts a transaction.
*
* @return void
*/
public function startTransaction();
/**
* This method is part of the RedBean Transaction Management
* mechanisms.
* Commits the transaction.
*
* @return void
*/
public function commit();
/**
* This method is part of the RedBean Transaction Management
* mechanisms.
* Rolls back the transaction.
*
* @return void
*/
public function rollback();
/**
* Closes database connection.
*
* @return void
*/
public function close();
/**
* Sets a driver specific option.
* Using this method you can access driver-specific functions.
* If the selected option exists the value will be passed and
* this method will return boolean TRUE, otherwise it will return
* boolean FALSE.
*
* @param string $optionKey option key
* @param string $optionValue option value
*
* @return boolean
*/
public function setOption( $optionKey, $optionValue );
/**
* Returns the version string from the database server.
*
* @return string
*/
public function getDatabaseServerVersion();
}

View File

@ -0,0 +1,293 @@
<?php
namespace RedBeanPHP\Adapter;
use RedBeanPHP\Observable as Observable;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\Driver as Driver;
/**
* DBAdapter (Database Adapter)
*
* An adapter class to connect various database systems to RedBean
* Database Adapter Class. The task of the database adapter class is to
* communicate with the database driver. You can use all sorts of database
* drivers with RedBeanPHP. The default database drivers that ships with
* the RedBeanPHP library is the RPDO driver ( which uses the PHP Data Objects
* Architecture aka PDO ).
*
* @file RedBeanPHP/Adapter/DBAdapter.php
* @author Gabor de Mooij and the RedBeanPHP Community.
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class DBAdapter extends Observable implements Adapter
{
/**
* @var Driver
*/
private $db = NULL;
/**
* @var string
*/
private $sql = '';
/**
* Constructor.
*
* Creates an instance of the RedBean Adapter Class.
* This class provides an interface for RedBean to work
* with ADO compatible DB instances.
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Driver $database ADO Compatible DB Instance
*/
public function __construct( $database )
{
$this->db = $database;
}
/**
* Returns a string containing the most recent SQL query
* processed by the database adapter, thus conforming to the
* interface:
*
* @see Adapter::getSQL
*
* Methods like get(), getRow() and exec() cause this SQL cache
* to get filled. If no SQL query has been processed yet this function
* will return an empty string.
*
* @return string
*/
public function getSQL()
{
return $this->sql;
}
/**
* @see Adapter::exec
*/
public function exec( $sql, $bindings = array(), $noevent = FALSE )
{
if ( !$noevent ) {
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
}
return $this->db->Execute( $sql, $bindings );
}
/**
* @see Adapter::get
*/
public function get( $sql, $bindings = array() )
{
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
return $this->db->GetAll( $sql, $bindings );
}
/**
* @see Adapter::getRow
*/
public function getRow( $sql, $bindings = array() )
{
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
return $this->db->GetRow( $sql, $bindings );
}
/**
* @see Adapter::getCol
*/
public function getCol( $sql, $bindings = array() )
{
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
return $this->db->GetCol( $sql, $bindings );
}
/**
* @see Adapter::getAssoc
*/
public function getAssoc( $sql, $bindings = array() )
{
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
$rows = $this->db->GetAll( $sql, $bindings );
if ( !$rows ) return array();
$assoc = array();
foreach ( $rows as $row ) {
if ( empty( $row ) ) continue;
$key = array_shift( $row );
switch ( count( $row ) ) {
case 0:
$value = $key;
break;
case 1:
$value = reset( $row );
break;
default:
$value = $row;
}
$assoc[$key] = $value;
}
return $assoc;
}
/**
* @see Adapter::getAssocRow
*/
public function getAssocRow($sql, $bindings = array())
{
$this->sql = $sql;
$this->signal( 'sql_exec', $this );
return $this->db->GetAssocRow( $sql, $bindings );
}
/**
* @see Adapter::getCell
*/
public function getCell( $sql, $bindings = array(), $noSignal = NULL )
{
$this->sql = $sql;
if ( !$noSignal ) $this->signal( 'sql_exec', $this );
return $this->db->GetOne( $sql, $bindings );
}
/**
* @see Adapter::getCursor
*/
public function getCursor( $sql, $bindings = array() )
{
return $this->db->GetCursor( $sql, $bindings );
}
/**
* @see Adapter::getInsertID
*/
public function getInsertID()
{
return $this->db->getInsertID();
}
/**
* @see Adapter::getAffectedRows
*/
public function getAffectedRows()
{
return $this->db->Affected_Rows();
}
/**
* @see Adapter::getDatabase
*/
public function getDatabase()
{
return $this->db;
}
/**
* @see Adapter::startTransaction
*/
public function startTransaction()
{
$this->db->StartTrans();
}
/**
* @see Adapter::commit
*/
public function commit()
{
$this->db->CommitTrans();
}
/**
* @see Adapter::rollback
*/
public function rollback()
{
$this->db->FailTrans();
}
/**
* @see Adapter::close.
*/
public function close()
{
$this->db->close();
}
/**
* Sets initialization code for connection.
*
* @param callable $code
*/
public function setInitCode($code) {
$this->db->setInitCode($code);
}
/**
* @see Adapter::setOption
*/
public function setOption( $optionKey, $optionValue ) {
if ( method_exists( $this->db, $optionKey ) ) {
call_user_func( array( $this->db, $optionKey ), $optionValue );
return TRUE;
}
return FALSE;
}
/**
* @see Adapter::getDatabaseServerVersion
*/
public function getDatabaseServerVersion()
{
return $this->db->DatabaseServerVersion();
}
}

View File

@ -0,0 +1,364 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* Association Manager.
* The association manager can be used to create and manage
* many-to-many relations (for example sharedLists). In a many-to-many relation,
* one bean can be associated with many other beans, while each of those beans
* can also be related to multiple beans.
*
* @file RedBeanPHP/AssociationManager.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class AssociationManager extends Observable
{
/**
* @var OODB
*/
protected $oodb;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var QueryWriter
*/
protected $writer;
/**
* Exception handler.
* Fluid and Frozen mode have different ways of handling
* exceptions. Fluid mode (using the fluid repository) ignores
* exceptions caused by the following:
*
* - missing tables
* - missing column
*
* In these situations, the repository will behave as if
* no beans could be found. This is because in fluid mode
* it might happen to query a table or column that has not been
* created yet. In frozen mode, this is not supposed to happen
* and the corresponding exceptions will be thrown.
*
* @param \Exception $exception exception
*
* @return void
*/
private function handleException( \Exception $exception )
{
if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
array(
QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
$exception->getDriverDetails()
)
) {
throw $exception;
}
}
/**
* Internal method.
* Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
* $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
*
* @param OODBBean $bean reference bean instance
* @param string $type target bean type
* @param string $sql additional SQL snippet
* @param array $bindings bindings for query
*
* @return array
*/
private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
{
$ids = array( $bean->id );
$sourceType = $bean->getMeta( 'type' );
try {
return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
return array();
}
}
/**
* Associates a pair of beans. This method associates two beans, no matter
* what types. Accepts a base bean that contains data for the linking record.
* This method is used by associate. This method also accepts a base bean to be used
* as the template for the link record in the database.
*
* @param OODBBean $bean1 first bean
* @param OODBBean $bean2 second bean
* @param OODBBean $bean base bean (association record)
*
* @return mixed
*/
protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
{
$type = $bean->getMeta( 'type' );
$property1 = $bean1->getMeta( 'type' ) . '_id';
$property2 = $bean2->getMeta( 'type' ) . '_id';
if ( $property1 == $property2 ) {
$property2 = $bean2->getMeta( 'type' ) . '2_id';
}
$this->oodb->store( $bean1 );
$this->oodb->store( $bean2 );
$bean->setMeta( "cast.$property1", "id" );
$bean->setMeta( "cast.$property2", "id" );
$bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
$bean->$property1 = $bean1->id;
$bean->$property2 = $bean2->id;
$results = array();
try {
$id = $this->oodb->store( $bean );
$results[] = $id;
} catch ( SQLException $exception ) {
if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ),
$exception->getDriverDetails() )
) {
throw $exception;
}
}
return $results;
}
/**
* Constructor, creates a new instance of the Association Manager.
* The association manager can be used to create and manage
* many-to-many relations (for example sharedLists). In a many-to-many relation,
* one bean can be associated with many other beans, while each of those beans
* can also be related to multiple beans. To create an Association Manager
* instance you'll need to pass a ToolBox object.
*
* @param ToolBox $tools toolbox supplying core RedBeanPHP objects
*/
public function __construct( ToolBox $tools )
{
$this->oodb = $tools->getRedBean();
$this->adapter = $tools->getDatabaseAdapter();
$this->writer = $tools->getWriter();
$this->toolbox = $tools;
}
/**
* Creates a table name based on a types array.
* Manages the get the correct name for the linking table for the
* types provided.
*
* @param array $types 2 types as strings
*
* @return string
*/
public function getTable( $types )
{
return $this->writer->getAssocTable( $types );
}
/**
* Associates two beans in a many-to-many relation.
* This method will associate two beans and store the connection between the
* two in a link table. Instead of two single beans this method also accepts
* two sets of beans. Returns the ID or the IDs of the linking beans.
*
* @param OODBBean|array $beans1 one or more beans to form the association
* @param OODBBean|array $beans2 one or more beans to form the association
*
* @return array
*/
public function associate( $beans1, $beans2 )
{
if ( !is_array( $beans1 ) ) {
$beans1 = array( $beans1 );
}
if ( !is_array( $beans2 ) ) {
$beans2 = array( $beans2 );
}
$results = array();
foreach ( $beans1 as $bean1 ) {
foreach ( $beans2 as $bean2 ) {
$table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
$bean = $this->oodb->dispense( $table );
$results[] = $this->associateBeans( $bean1, $bean2, $bean );
}
}
return ( count( $results ) > 1 ) ? $results : reset( $results );
}
/**
* Counts the number of related beans in an N-M relation.
* This method returns the number of beans of type $type associated
* with reference bean(s) $bean. The query can be tuned using an
* SQL snippet for additional filtering.
*
* @param OODBBean|array $bean a bean object or an array of beans
* @param string $type type of bean you're interested in
* @param string $sql SQL snippet (optional)
* @param array $bindings bindings for your SQL string
*
* @return integer
*/
public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
{
if ( !( $bean instanceof OODBBean ) ) {
throw new RedException(
'Expected array or OODBBean but got:' . gettype( $bean )
);
}
if ( !$bean->id ) {
return 0;
}
$beanType = $bean->getMeta( 'type' );
try {
return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
return 0;
}
}
/**
* Breaks the association between two beans. This method unassociates two beans. If the
* method succeeds the beans will no longer form an association. In the database
* this means that the association record will be removed. This method uses the
* OODB trash() method to remove the association links, thus giving FUSE models the
* opportunity to hook-in additional business logic. If the $fast parameter is
* set to boolean TRUE this method will remove the beans without their consent,
* bypassing FUSE. This can be used to improve performance.
*
* @param OODBBean $beans1 first bean in target association
* @param OODBBean $beans2 second bean in target association
* @param boolean $fast if TRUE, removes the entries by query without FUSE
*
* @return void
*/
public function unassociate( $beans1, $beans2, $fast = NULL )
{
$beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
$beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
foreach ( $beans1 as $bean1 ) {
foreach ( $beans2 as $bean2 ) {
try {
$this->oodb->store( $bean1 );
$this->oodb->store( $bean2 );
$type1 = $bean1->getMeta( 'type' );
$type2 = $bean2->getMeta( 'type' );
$row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
if ( !$row ) return;
$linkType = $this->getTable( array( $type1, $type2 ) );
if ( $fast ) {
$this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
return;
}
$beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
if ( count( $beans ) > 0 ) {
$bean = reset( $beans );
$this->oodb->trash( $bean );
}
} catch ( SQLException $exception ) {
$this->handleException( $exception );
}
}
}
}
/**
* Removes all relations for a bean. This method breaks every connection between
* a certain bean $bean and every other bean of type $type. Warning: this method
* is really fast because it uses a direct SQL query however it does not inform the
* models about this. If you want to notify FUSE models about deletion use a foreach-loop
* with unassociate() instead. (that might be slower though)
*
* @param OODBBean $bean reference bean
* @param string $type type of beans that need to be unassociated
*
* @return void
*/
public function clearRelations( OODBBean $bean, $type )
{
$this->oodb->store( $bean );
try {
$this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
}
}
/**
* Returns all the beans associated with $bean.
* This method will return an array containing all the beans that have
* been associated once with the associate() function and are still
* associated with the bean specified. The type parameter indicates the
* type of beans you are looking for. You can also pass some extra SQL and
* values for that SQL to filter your results after fetching the
* related beans.
*
* Don't try to make use of subqueries, a subquery using IN() seems to
* be slower than two queries!
*
* Since 3.2, you can now also pass an array of beans instead just one
* bean as the first parameter.
*
* @param OODBBean|array $bean the bean you have
* @param string $type the type of beans you want
* @param string $sql SQL snippet for extra filtering
* @param array $bindings values to be inserted in SQL slots
*
* @return array
*/
public function related( $bean, $type, $sql = '', $bindings = array() )
{
$sql = $this->writer->glueSQLCondition( $sql );
$rows = $this->relatedRows( $bean, $type, $sql, $bindings );
$links = array();
foreach ( $rows as $key => $row ) {
if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
$links[$row['id']][] = $row['linked_by'];
unset( $rows[$key]['linked_by'] );
}
$beans = $this->oodb->convertToBeans( $type, $rows );
foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
return $beans;
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Cursor as Cursor;
use RedBeanPHP\Repository as Repository;
/**
* BeanCollection.
*
* The BeanCollection represents a collection of beans and
* makes it possible to use database cursors. The BeanCollection
* has a method next() to obtain the first, next and last bean
* in the collection. The BeanCollection does not implement the array
* interface nor does it try to act like an array because it cannot go
* backward or rewind itself.
*
* Use the BeanCollection for large datasets where skip/limit is not an
* option. Keep in mind that ID-marking (querying a start ID) is a decent
* alternative though.
*
* @file RedBeanPHP/BeanCollection.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class BeanCollection
{
/**
* @var Cursor
*/
protected $cursor = NULL;
/**
* @var Repository
*/
protected $repository = NULL;
/**
* @var string
*/
protected $type = NULL;
/**
* Constructor, creates a new instance of the BeanCollection.
*
* @param string $type type of beans in this collection
* @param Repository $repository repository to use to generate bean objects
* @param Cursor $cursor cursor object to use
*
* @return void
*/
public function __construct( $type, Repository $repository, Cursor $cursor )
{
$this->type = $type;
$this->cursor = $cursor;
$this->repository = $repository;
}
/**
* Returns the next bean in the collection.
* If called the first time, this will return the first bean in the collection.
* If there are no more beans left in the collection, this method
* will return NULL.
*
* @return OODBBean|NULL
*/
public function next()
{
$row = $this->cursor->getNextItem();
if ( $row ) {
$beans = $this->repository->convertToBeans( $this->type, array( $row ) );
return reset( $beans );
}
return NULL;
}
/**
* Resets the collection from the start, like a fresh() on a bean.
*
* @return void
*/
public function reset()
{
$this->cursor->reset();
}
/**
* Closes the underlying cursor (needed for some databases).
*
* @return void
*/
public function close()
{
$this->cursor->close();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Bean Helper Interface.
*
* Interface for Bean Helper.
* A little bolt that glues the whole machinery together.
* The Bean Helper is passed to the OODB RedBeanPHP Object to
* faciliatte the creation of beans and providing them with
* a toolbox. The Helper also facilitates the FUSE feature,
* determining how beans relate to their models. By overriding
* the getModelForBean method you can tune the FUSEing to
* fit your business application needs.
*
* @file RedBeanPHP/IBeanHelper.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface BeanHelper
{
/**
* Returns a toolbox to empower the bean.
* This allows beans to perform OODB operations by themselves,
* as such the bean is a proxy for OODB. This allows beans to implement
* their magic getters and setters and return lists.
*
* @return ToolBox
*/
public function getToolbox();
/**
* Does approximately the same as getToolbox but also extracts the
* toolbox for you.
* This method returns a list with all toolbox items in Toolbox Constructor order:
* OODB, adapter, writer and finally the toolbox itself!.
*
* @return array
*/
public function getExtractedToolbox();
/**
* Given a certain bean this method will
* return the corresponding model.
*
* @param OODBBean $bean bean to obtain the corresponding model of
*
* @return SimpleModel|CustomModel|NULL
*/
public function getModelForBean( OODBBean $bean );
}

View File

@ -0,0 +1,109 @@
<?php
namespace RedBeanPHP\BeanHelper;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\Facade as Facade;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
/**
* Bean Helper.
*
* The Bean helper helps beans to access access the toolbox and
* FUSE models. This Bean Helper makes use of the facade to obtain a
* reference to the toolbox.
*
* @file RedBeanPHP/BeanHelperFacade.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SimpleFacadeBeanHelper implements BeanHelper
{
/**
* Factory function to create instance of Simple Model, if any.
*
* @var \Closure
*/
private static $factory = null;
/**
* Factory method using a customizable factory function to create
* the instance of the Simple Model.
*
* @param string $modelClassName name of the class
*
* @return SimpleModel
*/
public static function factory( $modelClassName )
{
$factory = self::$factory;
return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
}
/**
* Sets the factory function to create the model when using FUSE
* to connect a bean to a model.
*
* @param \Closure $factory factory function
*
* @return void
*/
public static function setFactoryFunction( $factory )
{
self::$factory = $factory;
}
/**
* @see BeanHelper::getToolbox
*/
public function getToolbox()
{
return Facade::getToolBox();
}
/**
* @see BeanHelper::getModelForBean
*/
public function getModelForBean( OODBBean $bean )
{
$model = $bean->getMeta( 'type' );
$prefix = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
if ( strpos( $model, '_' ) !== FALSE ) {
$modelParts = explode( '_', $model );
$modelName = '';
foreach( $modelParts as $part ) {
$modelName .= ucfirst( $part );
}
$modelName = $prefix . $modelName;
if ( !class_exists( $modelName ) ) {
$modelName = $prefix . ucfirst( $model );
if ( !class_exists( $modelName ) ) {
return NULL;
}
}
} else {
$modelName = $prefix . ucfirst( $model );
if ( !class_exists( $modelName ) ) {
return NULL;
}
}
$obj = self::factory( $modelName );
$obj->loadBean( $bean );
return $obj;
}
/**
* @see BeanHelper::getExtractedToolbox
*/
public function getExtractedToolbox()
{
return Facade::getExtractedToolbox();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace RedBeanPHP;
/**
* Database Cursor Interface.
* A cursor is used by Query Writers to fetch Query Result rows
* one row at a time. This is useful if you expect the result set to
* be quite large. This interface dscribes the API of a database
* cursor. There can be multiple implementations of the Cursor,
* by default RedBeanPHP offers the PDOCursor for drivers shipping
* with RedBeanPHP and the NULLCursor.
*
* @file RedBeanPHP/Cursor.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Cursor
{
/**
* Should retrieve the next row of the result set.
* This method is used to iterate over the result set.
*
* @return array
*/
public function getNextItem();
/**
* Resets the cursor by closing it and re-executing the statement.
* This reloads fresh data from the database for the whole collection.
*
* @return void
*/
public function reset();
/**
* Closes the database cursor.
* Some databases require a cursor to be closed before executing
* another statement/opening a new cursor.
*
* @return void
*/
public function close();
}

View File

@ -0,0 +1,48 @@
<?php
namespace RedBeanPHP\Cursor;
use RedBeanPHP\Cursor as Cursor;
/**
* NULL Database Cursor
* Implementation of the NULL Cursor.
* Used for an empty BeanCollection. This Cursor
* can be used for instance if a query fails but the interface
* demands a cursor to be returned.
*
* @file RedBeanPHP/Cursor/NULLCursor.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class NullCursor implements Cursor
{
/**
* @see Cursor::getNextItem
*/
public function getNextItem()
{
return NULL;
}
/**
* @see Cursor::reset
*/
public function reset()
{
return NULL;
}
/**
* @see Cursor::close
*/
public function close()
{
return NULL;
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace RedBeanPHP\Cursor;
use RedBeanPHP\Cursor as Cursor;
/**
* PDO Database Cursor
* Implementation of PDO Database Cursor.
* Used by the BeanCollection to fetch one bean at a time.
* The PDO Cursor is used by Query Writers to support retrieval
* of large bean collections. For instance, this class is used to
* implement the findCollection()/BeanCollection functionality.
*
* @file RedBeanPHP/Cursor/PDOCursor.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class PDOCursor implements Cursor
{
/**
* @var PDOStatement
*/
protected $res;
/**
* @var string
*/
protected $fetchStyle;
/**
* Constructor, creates a new instance of a PDO Database Cursor.
*
* @param PDOStatement $res the PDO statement
* @param string $fetchStyle fetch style constant to use
*
* @return void
*/
public function __construct( \PDOStatement $res, $fetchStyle )
{
$this->res = $res;
$this->fetchStyle = $fetchStyle;
}
/**
* @see Cursor::getNextItem
*/
public function getNextItem()
{
return $this->res->fetch();
}
/**
* @see Cursor::reset
*/
public function reset()
{
$this->close();
$this->res->execute();
}
/**
* @see Cursor::close
*/
public function close()
{
$this->res->closeCursor();
}
}

View File

@ -0,0 +1,194 @@
<?php
namespace RedBeanPHP;
/**
* Interface for database drivers.
* The Driver API conforms to the ADODB pseudo standard
* for database drivers.
*
* @file RedBeanPHP/Driver.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Driver
{
/**
* Runs a query and fetches results as a multi dimensional array.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return array
*/
public function GetAll( $sql, $bindings = array() );
/**
* Runs a query and fetches results as a column.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return array
*/
public function GetCol( $sql, $bindings = array() );
/**
* Runs a query and returns results as a single cell.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return mixed
*/
public function GetOne( $sql, $bindings = array() );
/**
* Runs a query and returns results as an associative array
* indexed by the first column.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return mixed
*/
public function GetAssocRow( $sql, $bindings = array() );
/**
* Runs a query and returns a flat array containing the values of
* one row.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return array
*/
public function GetRow( $sql, $bindings = array() );
/**
* Executes SQL code and allows key-value binding.
* This function allows you to provide an array with values to bind
* to query parameters. For instance you can bind values to question
* marks in the query. Each value in the array corresponds to the
* question mark in the query that matches the position of the value in the
* array. You can also bind values using explicit keys, for instance
* array(":key"=>123) will bind the integer 123 to the key :key in the
* SQL. This method has no return value.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return array Affected Rows
*/
public function Execute( $sql, $bindings = array() );
/**
* Returns the latest insert ID if driver does support this
* feature.
*
* @return integer
*/
public function GetInsertID();
/**
* Returns the number of rows affected by the most recent query
* if the currently selected driver driver supports this feature.
*
* @return integer
*/
public function Affected_Rows();
/**
* Returns a cursor-like object from the database.
*
* @param string $sql SQL query to execute
* @param array $bindings list of values to bind to SQL snippet
*
* @return mixed
*/
public function GetCursor( $sql, $bindings = array() );
/**
* Toggles debug mode. In debug mode the driver will print all
* SQL to the screen together with some information about the
* results.
*
* This method is for more fine-grained control. Normally
* you should use the facade to start the query debugger for
* you. The facade will manage the object wirings necessary
* to use the debugging functionality.
*
* Usage (through facade):
*
* <code>
* R::debug( TRUE );
* ...rest of program...
* R::debug( FALSE );
* </code>
*
* The example above illustrates how to use the RedBeanPHP
* query debugger through the facade.
*
* @param boolean $trueFalse turn on/off
* @param Logger $logger logger instance
*
* @return void
*/
public function setDebugMode( $tf, $customLogger );
/**
* Starts a transaction.
*
* @return void
*/
public function CommitTrans();
/**
* Commits a transaction.
*
* @return void
*/
public function StartTrans();
/**
* Rolls back a transaction.
*
* @return void
*/
public function FailTrans();
/**
* Resets the internal Query Counter.
*
* @return self
*/
public function resetCounter();
/**
* Returns the number of SQL queries processed.
*
* @return integer
*/
public function getQueryCount();
/**
* Sets initialization code for connection.
*
* @param callable $code code
*
* @return void
*/
public function setInitCode( $code );
/**
* Returns the version string from the database server.
*
* @return string
*/
public function DatabaseServerVersion();
}

View File

@ -0,0 +1,950 @@
<?php
namespace RedBeanPHP\Driver;
use RedBeanPHP\Driver as Driver;
use RedBeanPHP\Logger as Logger;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\Logger\RDefault as RDefault;
use RedBeanPHP\PDOCompatible as PDOCompatible;
use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
/**
* PDO Driver
* This Driver implements the RedBean Driver API.
* for RedBeanPHP. This is the standard / default database driver
* for RedBeanPHP.
*
* @file RedBeanPHP/PDO.php
* @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
* @license BSD/GPLv2
*
* @copyright
* copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class RPDO implements Driver
{
/**
* @var integer
*/
protected $max;
/**
* @var string
*/
protected $dsn;
/**
* @var boolean
*/
protected $loggingEnabled = FALSE;
/**
* @var Logger
*/
protected $logger = NULL;
/**
* @var PDO
*/
protected $pdo;
/**
* @var integer
*/
protected $affectedRows;
/**
* @var integer
*/
protected $resultArray;
/**
* @var array
*/
protected $connectInfo = array();
/**
* @var boolean
*/
protected $isConnected = FALSE;
/**
* @var bool
*/
protected $flagUseStringOnlyBinding = FALSE;
/**
* @var integer
*/
protected $queryCounter = 0;
/**
* @var string
*/
protected $mysqlCharset = '';
/**
* @var string
*/
protected $mysqlCollate = '';
/**
* @var boolean
*/
protected $stringifyFetches = TRUE;
/**
* @var string
*/
protected $initSQL = NULL;
/**
* @var callable
*/
protected $initCode = NULL;
/**
* Binds parameters. This method binds parameters to a PDOStatement for
* Query Execution. This method binds parameters as NULL, INTEGER or STRING
* and supports both named keys and question mark keys.
*
* @param PDOStatement $statement PDO Statement instance
* @param array $bindings values that need to get bound to the statement
*
* @return void
*/
protected function bindParams( $statement, $bindings )
{
foreach ( $bindings as $key => &$value ) {
$k = is_integer( $key ) ? $key + 1 : $key;
if ( is_array( $value ) && count( $value ) == 2 ) {
$paramType = end( $value );
$value = reset( $value );
} else {
$paramType = NULL;
}
if ( is_null( $value ) ) {
$statement->bindValue( $k, NULL, \PDO::PARAM_NULL );
continue;
}
if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) {
if ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
$paramType = \PDO::PARAM_INT;
} else {
$paramType = \PDO::PARAM_STR;
}
}
$statement->bindParam( $k, $value, $paramType );
}
}
/**
* This method runs the actual SQL query and binds a list of parameters to the query.
* slots. The result of the query will be stored in the protected property
* $rs (always array). The number of rows affected (result of rowcount, if supported by database)
* is stored in protected property $affectedRows. If the debug flag is set
* this function will send debugging output to screen buffer.
*
* @param string $sql the SQL string to be send to database server
* @param array $bindings the values that need to get bound to the query slots
* @param array $options
*
* @return mixed
* @throws SQL
*/
protected function runQuery( $sql, $bindings, $options = array() )
{
$this->connect();
if ( $this->loggingEnabled && $this->logger ) {
$this->logger->log( $sql, $bindings );
}
try {
if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
if (defined('\\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT')) {
$statement = @$this->pdo->prepare($sql, array(\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE));
} else {
$statement = $this->pdo->prepare($sql);
}
} else {
$statement = $this->pdo->prepare( $sql );
}
$this->bindParams( $statement, $bindings );
$statement->execute();
$this->queryCounter ++;
$this->affectedRows = $statement->rowCount();
if ( $statement->columnCount() ) {
$fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
$this->resultArray = array();
return $statement;
}
$this->resultArray = $statement->fetchAll( $fetchStyle );
if ( $this->loggingEnabled && $this->logger ) {
$this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
}
} else {
$this->resultArray = array();
}
} catch ( \PDOException $e ) {
//Unfortunately the code field is supposed to be int by default (php)
//So we need a property to convey the SQL State code.
$err = $e->getMessage();
if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
$exception = new SQL( $err, 0, $e );
$exception->setSQLState( $e->getCode() );
$exception->setDriverDetails( $e->errorInfo );
throw $exception;
}
}
/**
* Try to fix MySQL character encoding problems.
* MySQL < 5.5.3 does not support proper 4 byte unicode but they
* seem to have added it with version 5.5.3 under a different label: utf8mb4.
* We try to select the best possible charset based on your version data.
*
* @return void
*/
protected function setEncoding()
{
$driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
if ($driver === 'mysql') {
$charset = $this->hasCap( 'utf8mb4' ) ? 'utf8mb4' : 'utf8';
$collate = $this->hasCap( 'utf8mb4_520' ) ? '_unicode_520_ci' : '_unicode_ci';
$this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '. $charset ); //on every re-connect
/* #624 removed space before SET NAMES because it causes trouble with ProxySQL */
$this->pdo->exec('SET NAMES '. $charset); //also for current connection
$this->mysqlCharset = $charset;
$this->mysqlCollate = $charset . $collate;
}
}
/**
* Determine if a database supports a particular feature.
* Currently this function can be used to detect the following features:
*
* - utf8mb4
* - utf8mb4 520
*
* Usage:
*
* <code>
* $this->hasCap( 'utf8mb4_520' );
* </code>
*
* By default, RedBeanPHP uses this method under the hood to make sure
* you use the latest UTF8 encoding possible for your database.
*
* @param $db_cap identifier of database capability
*
* @return int|false Whether the database feature is supported, FALSE otherwise.
**/
protected function hasCap( $db_cap )
{
$compare = FALSE;
$version = $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION );
switch ( strtolower( $db_cap ) ) {
case 'utf8mb4':
//oneliner, to boost code coverage (coverage does not span versions)
if ( version_compare( $version, '5.5.3', '<' ) ) { return FALSE; }
$client_version = $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
/*
* libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
* mysqlnd has supported utf8mb4 since 5.0.9.
*/
if ( strpos( $client_version, 'mysqlnd' ) !== FALSE ) {
$client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
$compare = version_compare( $client_version, '5.0.9', '>=' );
} else {
$compare = version_compare( $client_version, '5.5.3', '>=' );
}
break;
case 'utf8mb4_520':
$compare = version_compare( $version, '5.6', '>=' );
break;
}
return $compare;
}
/**
* Constructor. You may either specify dsn, user and password or
* just give an existing PDO connection.
*
* Usage:
*
* <code>
* $driver = new RPDO( $dsn, $user, $password );
* </code>
*
* The example above illustrates how to create a driver
* instance from a database connection string (dsn), a username
* and a password. It's also possible to pass a PDO object.
*
* Usage:
*
* <code>
* $driver = new RPDO( $existingConnection );
* </code>
*
* The second example shows how to create an RPDO instance
* from an existing PDO object.
*
* @param string|object $dsn database connection string
* @param string $user optional, usename to sign in
* @param string $pass optional, password for connection login
*
* @return void
*/
public function __construct( $dsn, $user = NULL, $pass = NULL, $options = array() )
{
if ( is_object( $dsn ) ) {
$this->pdo = $dsn;
$this->isConnected = TRUE;
$this->setEncoding();
$this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
$this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
// make sure that the dsn at least contains the type
$this->dsn = $this->getDatabaseType();
} else {
$this->dsn = $dsn;
$this->connectInfo = array( 'pass' => $pass, 'user' => $user );
if (is_array($options)) $this->connectInfo['options'] = $options;
}
//PHP 5.3 PDO SQLite has a bug with large numbers:
if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
$this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
} elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
$this->max = 2147483647; //bindParam in pdo_cubrid also fails...
} else {
$this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
}
}
/**
* Sets PDO in stringify fetch mode.
* If set to TRUE, this method will make sure all data retrieved from
* the database will be fetched as a string. Default: TRUE.
*
* To set it to FALSE...
*
* Usage:
*
* <code>
* R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE );
* </code>
*
* Important!
* Note, this method only works if you set the value BEFORE the connection
* has been establish. Also, this setting ONLY works with SOME drivers.
* It's up to the driver to honour this setting.
*
* @param boolean $bool
*/
public function stringifyFetches( $bool ) {
$this->stringifyFetches = $bool;
}
/**
* Returns the best possible encoding for MySQL based on version data.
* This method can be used to obtain the best character set parameters
* possible for your database when constructing a table creation query
* containing clauses like: CHARSET=... COLLATE=...
* This is a MySQL-specific method and not part of the driver interface.
*
* Usage:
*
* <code>
* $charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
* </code>
*
* @param boolean $retCol pass TRUE to return both charset/collate
*
* @return string|array
*/
public function getMysqlEncoding( $retCol = FALSE )
{
if( $retCol )
return array( 'charset' => $this->mysqlCharset, 'collate' => $this->mysqlCollate );
return $this->mysqlCharset;
}
/**
* Whether to bind all parameters as strings.
* If set to TRUE this will cause all integers to be bound as STRINGS.
* This will NOT affect NULL values.
*
* @param boolean $yesNo pass TRUE to bind all parameters as strings.
*
* @return void
*/
public function setUseStringOnlyBinding( $yesNo )
{
$this->flagUseStringOnlyBinding = (boolean) $yesNo;
if ( $this->loggingEnabled && $this->logger && method_exists($this->logger,'setUseStringOnlyBinding')) {
$this->logger->setUseStringOnlyBinding( $this->flagUseStringOnlyBinding );
}
}
/**
* Sets the maximum value to be bound as integer, normally
* this value equals PHP's MAX INT constant, however sometimes
* PDO driver bindings cannot bind large integers as integers.
* This method allows you to manually set the max integer binding
* value to manage portability/compatibility issues among different
* PHP builds. This method will return the old value.
*
* @param integer $max maximum value for integer bindings
*
* @return integer
*/
public function setMaxIntBind( $max )
{
if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
$oldMax = $this->max;
$this->max = $max;
return $oldMax;
}
/**
* Sets initialization code to execute upon connecting.
*
* @param callable $code
*
* @return void
*/
public function setInitCode($code)
{
$this->initCode= $code;
}
/**
* Establishes a connection to the database using PHP\PDO
* functionality. If a connection has already been established this
* method will simply return directly. This method also turns on
* UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
* PDO-FETCH-ASSOC.
*
* @return void
*/
public function connect()
{
if ( $this->isConnected ) return;
try {
$user = $this->connectInfo['user'];
$pass = $this->connectInfo['pass'];
$options = array();
if (isset($this->connectInfo['options']) && is_array($this->connectInfo['options'])) {
$options = $this->connectInfo['options'];
}
$this->pdo = new \PDO( $this->dsn, $user, $pass, $options );
$this->setEncoding();
$this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $this->stringifyFetches );
//cant pass these as argument to constructor, CUBRID driver does not understand...
$this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
$this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
$this->isConnected = TRUE;
/* run initialisation query if any */
if ( $this->initSQL !== NULL ) {
$this->Execute( $this->initSQL );
$this->initSQL = NULL;
}
if ( $this->initCode !== NULL ) {
$code = $this->initCode;
$code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
}
} catch ( \PDOException $exception ) {
$matches = array();
$dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
}
}
/**
* Directly sets PDO instance into driver.
* This method might improve performance, however since the driver does
* not configure this instance terrible things may happen... only use
* this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
* you know your database server VERY WELL.
*
* - connected TRUE|FALSE (treat this instance as connected, default: TRUE)
* - setEncoding TRUE|FALSE (let RedBeanPHP set encoding for you, default: TRUE)
* - setAttributes TRUE|FALSE (let RedBeanPHP set attributes for you, default: TRUE)*
* - setDSNString TRUE|FALSE (extract DSN string from PDO instance, default: TRUE)
* - stringFetch TRUE|FALSE (whether you want to stringify fetches or not, default: TRUE)
* - runInitCode TRUE|FALSE (run init code if any, default: TRUE)
*
* *attributes:
* - RedBeanPHP will ask database driver to throw Exceptions on errors (recommended for compatibility)
* - RedBeanPHP will ask database driver to use associative arrays when fetching (recommended for compatibility)
*
* @param PDO $pdo PDO instance
* @param array $options Options to apply
*
* @return void
*/
public function setPDO( \PDO $pdo, $options = array() ) {
$this->pdo = $pdo;
$connected = TRUE;
$setEncoding = TRUE;
$setAttributes = TRUE;
$setDSNString = TRUE;
$runInitCode = TRUE;
$stringFetch = TRUE;
if ( isset($options['connected']) ) $connected = $options['connected'];
if ( isset($options['setEncoding']) ) $setEncoding = $options['setEncoding'];
if ( isset($options['setAttributes']) ) $setAttributes = $options['setAttributes'];
if ( isset($options['setDSNString']) ) $setDSNString = $options['setDSNString'];
if ( isset($options['runInitCode']) ) $runInitCode = $options['runInitCode'];
if ( isset($options['stringFetch']) ) $stringFetch = $options['stringFetch'];
if ($connected) $this->connected = $connected;
if ($setEncoding) $this->setEncoding();
if ($setAttributes) {
$this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
$this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
$this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $stringFetch );
}
if ($runInitCode) {
/* run initialisation query if any */
if ( $this->initSQL !== NULL ) {
$this->Execute( $this->initSQL );
$this->initSQL = NULL;
}
if ( $this->initCode !== NULL ) {
$code = $this->initCode;
$code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
}
}
if ($setDSNString) $this->dsn = $this->getDatabaseType();
}
/**
* @see Driver::GetAll
*/
public function GetAll( $sql, $bindings = array() )
{
$this->runQuery( $sql, $bindings );
return $this->resultArray;
}
/**
* @see Driver::GetAssocRow
*/
public function GetAssocRow( $sql, $bindings = array() )
{
$this->runQuery( $sql, $bindings, array(
'fetchStyle' => \PDO::FETCH_ASSOC
)
);
return $this->resultArray;
}
/**
* @see Driver::GetCol
*/
public function GetCol( $sql, $bindings = array() )
{
$rows = $this->GetAll( $sql, $bindings );
if ( empty( $rows ) || !is_array( $rows ) ) {
return array();
}
$cols = array();
foreach ( $rows as $row ) {
$cols[] = reset( $row );
}
return $cols;
}
/**
* @see Driver::GetOne
*/
public function GetOne( $sql, $bindings = array() )
{
$arr = $this->GetAll( $sql, $bindings );
if ( empty( $arr[0] ) || !is_array( $arr[0] ) ) {
return NULL;
}
return reset( $arr[0] );
}
/**
* Alias for getOne().
* Backward compatibility.
*
* @param string $sql SQL
* @param array $bindings bindings
*
* @return mixed
*/
public function GetCell( $sql, $bindings = array() )
{
return $this->GetOne( $sql, $bindings );
}
/**
* @see Driver::GetRow
*/
public function GetRow( $sql, $bindings = array() )
{
$arr = $this->GetAll( $sql, $bindings );
if ( is_array( $arr ) && count( $arr ) ) {
return reset( $arr );
}
return array();
}
/**
* @see Driver::Excecute
*/
public function Execute( $sql, $bindings = array() )
{
$this->runQuery( $sql, $bindings );
return $this->affectedRows;
}
/**
* @see Driver::GetInsertID
*/
public function GetInsertID()
{
$this->connect();
return (int) $this->pdo->lastInsertId();
}
/**
* @see Driver::GetCursor
*/
public function GetCursor( $sql, $bindings = array() )
{
$statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
$cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
return $cursor;
}
/**
* @see Driver::Affected_Rows
*/
public function Affected_Rows()
{
$this->connect();
return (int) $this->affectedRows;
}
/**
* @see Driver::setDebugMode
*/
public function setDebugMode( $tf, $logger = NULL )
{
$this->connect();
$this->loggingEnabled = (bool) $tf;
if ( $this->loggingEnabled and !$logger ) {
$logger = new RDefault();
}
$this->setLogger( $logger );
}
/**
* Injects Logger object.
* Sets the logger instance you wish to use.
*
* This method is for more fine-grained control. Normally
* you should use the facade to start the query debugger for
* you. The facade will manage the object wirings necessary
* to use the debugging functionality.
*
* Usage (through facade):
*
* <code>
* R::debug( TRUE );
* ...rest of program...
* R::debug( FALSE );
* </code>
*
* The example above illustrates how to use the RedBeanPHP
* query debugger through the facade.
*
* @param Logger $logger the logger instance to be used for logging
*
* @return self
*/
public function setLogger( Logger $logger )
{
$this->logger = $logger;
return $this;
}
/**
* Gets Logger object.
* Returns the currently active Logger instance.
*
* @return Logger
*/
public function getLogger()
{
return $this->logger;
}
/**
* @see Driver::StartTrans
*/
public function StartTrans()
{
$this->connect();
$this->pdo->beginTransaction();
}
/**
* @see Driver::CommitTrans
*/
public function CommitTrans()
{
$this->connect();
$this->pdo->commit();
}
/**
* @see Driver::FailTrans
*/
public function FailTrans()
{
$this->connect();
$this->pdo->rollback();
}
/**
* Returns the name of database driver for PDO.
* Uses the PDO attribute DRIVER NAME to obtain the name of the
* PDO driver. Use this method to identify the current PDO driver
* used to provide access to the database. Example of a database
* driver string:
*
* <code>
* mysql
* </code>
*
* Usage:
*
* <code>
* echo R::getDatabaseAdapter()->getDatabase()->getDatabaseType();
* </code>
*
* The example above prints the current database driver string to
* stdout.
*
* Note that this is a driver-specific method, not part of the
* driver interface. This method might not be available in other
* drivers since it relies on PDO.
*
* @return string
*/
public function getDatabaseType()
{
$this->connect();
return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
}
/**
* Returns the version identifier string of the database client.
* This method can be used to identify the currently installed
* database client. Note that this method will also establish a connection
* (because this is required to obtain the version information).
*
* Example of a version string:
*
* <code>
* mysqlnd 5.0.12-dev - 20150407 - $Id: b5c5906d452ec590732a93b051f3827e02749b83 $
* </code>
*
* Usage:
*
* <code>
* echo R::getDatabaseAdapter()->getDatabase()->getDatabaseVersion();
* </code>
*
* The example above will print the version string to stdout.
*
* Note that this is a driver-specific method, not part of the
* driver interface. This method might not be available in other
* drivers since it relies on PDO.
*
* To obtain the database server version, use getDatabaseServerVersion()
* instead.
*
* @return mixed
*/
public function getDatabaseVersion()
{
$this->connect();
return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
}
/**
* Returns the underlying PHP PDO instance.
* For some low-level database operations you'll need access to the PDO
* object. Not that this method is only available in RPDO and other
* PDO based database drivers for RedBeanPHP. Other drivers may not have
* a method like this. The following example demonstrates how to obtain
* a reference to the PDO instance from the facade:
*
* Usage:
*
* <code>
* $pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
* </code>
*
* @return PDO
*/
public function getPDO()
{
$this->connect();
return $this->pdo;
}
/**
* Closes the database connection.
* While database connections are closed automatically at the end of the PHP script,
* closing database connections is generally recommended to improve performance.
* Closing a database connection will immediately return the resources to PHP.
*
* Usage:
*
* <code>
* R::setup( ... );
* ... do stuff ...
* R::close();
* </code>
*
* @return void
*/
public function close()
{
$this->pdo = NULL;
$this->isConnected = FALSE;
}
/**
* Returns TRUE if the current PDO instance is connected.
*
* @return boolean
*/
public function isConnected()
{
return $this->isConnected && $this->pdo;
}
/**
* Toggles logging, enables or disables logging.
*
* @param boolean $enable TRUE to enable logging
*
* @return self
*/
public function setEnableLogging( $enable )
{
$this->loggingEnabled = (boolean) $enable;
return $this;
}
/**
* Resets the query counter.
* The query counter can be used to monitor the number
* of database queries that have
* been processed according to the database driver. You can use this
* to monitor the number of queries required to render a page.
*
* Usage:
*
* <code>
* R::resetQueryCount();
* echo R::getQueryCount() . ' queries processed.';
* </code>
*
* @return self
*/
public function resetCounter()
{
$this->queryCounter = 0;
return $this;
}
/**
* Returns the number of SQL queries processed.
* This method returns the number of database queries that have
* been processed according to the database driver. You can use this
* to monitor the number of queries required to render a page.
*
* Usage:
*
* <code>
* echo R::getQueryCount() . ' queries processed.';
* </code>
*
* @return integer
*/
public function getQueryCount()
{
return $this->queryCounter;
}
/**
* Returns the maximum value treated as integer parameter
* binding.
*
* This method is mainly for testing purposes but it can help
* you solve some issues relating to integer bindings.
*
* @return integer
*/
public function getIntegerBindingMax()
{
return $this->max;
}
/**
* Sets a query to be executed upon connecting to the database.
* This method provides an opportunity to configure the connection
* to a database through an SQL-based interface. Objects can provide
* an SQL string to be executed upon establishing a connection to
* the database. This has been used to solve issues with default
* foreign key settings in SQLite3 for instance, see Github issues:
* #545 and #548.
*
* @param string $sql SQL query to run upon connecting to database
*
* @return self
*/
public function setInitQuery( $sql ) {
$this->initSQL = $sql;
return $this;
}
/**
* Returns the version string from the database server.
*
* @return string
*/
public function DatabaseServerVersion() {
return trim( strval( $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION) ) );
}
}

View File

@ -0,0 +1,467 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
/**
* Duplication Manager
* The Duplication Manager creates deep copies from beans, this means
* it can duplicate an entire bean hierarchy. You can use this feature to
* implement versioning for instance. Because duplication and exporting are
* closely related this class is also used to export beans recursively
* (i.e. we make a duplicate and then convert to array). This class allows
* you to tune the duplication process by specifying filters determining
* which relations to take into account and by specifying tables
* (in which case no reflective queries have to be issued thus improving
* performance). This class also hosts the Camelfy function used to
* reformat the keys of an array, this method is publicly available and
* used internally by exportAll().
*
* @file RedBeanPHP/DuplicationManager.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class DuplicationManager
{
/**
* @var ToolBox
*/
protected $toolbox;
/**
* @var AssociationManager
*/
protected $associationManager;
/**
* @var OODB
*/
protected $redbean;
/**
* @var array
*/
protected $tables = array();
/**
* @var array
*/
protected $columns = array();
/**
* @var array
*/
protected $filters = array();
/**
* @var array
*/
protected $cacheTables = FALSE;
/**
* @var boolean
*/
protected $copyMeta = FALSE;
/**
* Copies the shared beans in a bean, i.e. all the sharedBean-lists.
*
* @param OODBBean $copy target bean to copy lists to
* @param string $shared name of the shared list
* @param array $beans array with shared beans to copy
*
* @return void
*/
private function copySharedBeans( OODBBean $copy, $shared, $beans )
{
$copy->$shared = array();
foreach ( $beans as $subBean ) {
array_push( $copy->$shared, $subBean );
}
}
/**
* Copies the own beans in a bean, i.e. all the ownBean-lists.
* Each bean in the own-list belongs exclusively to its owner so
* we need to invoke the duplicate method again to duplicate each bean here.
*
* @param OODBBean $copy target bean to copy lists to
* @param string $owned name of the own list
* @param array $beans array with shared beans to copy
* @param array $trail array with former beans to detect recursion
* @param boolean $preserveIDs TRUE means preserve IDs, for export only
*
* @return void
*/
private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
{
$copy->$owned = array();
foreach ( $beans as $subBean ) {
array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
}
}
/**
* Creates a copy of bean $bean and copies all primitive properties (not lists)
* and the parents beans to the newly created bean. Also sets the ID of the bean
* to 0.
*
* @param OODBBean $bean bean to copy
*
* @return OODBBean
*/
private function createCopy( OODBBean $bean )
{
$type = $bean->getMeta( 'type' );
$copy = $this->redbean->dispense( $type );
$copy->setMeta( 'sys.dup-from-id', $bean->id );
$copy->setMeta( 'sys.old-id', $bean->id );
$copy->importFrom( $bean );
if ($this->copyMeta) $copy->copyMetaFrom($bean);
$copy->id = 0;
return $copy;
}
/**
* Generates a key from the bean type and its ID and determines if the bean
* occurs in the trail, if not the bean will be added to the trail.
* Returns TRUE if the bean occurs in the trail and FALSE otherwise.
*
* @param array $trail list of former beans
* @param OODBBean $bean currently selected bean
*
* @return boolean
*/
private function inTrailOrAdd( &$trail, OODBBean $bean )
{
$type = $bean->getMeta( 'type' );
$key = $type . $bean->getID();
if ( isset( $trail[$key] ) ) {
return TRUE;
}
$trail[$key] = $bean;
return FALSE;
}
/**
* Given the type name of a bean this method returns the canonical names
* of the own-list and the shared-list properties respectively.
* Returns a list with two elements: name of the own-list, and name
* of the shared list.
*
* @param string $typeName bean type name
*
* @return array
*/
private function getListNames( $typeName )
{
$owned = 'own' . ucfirst( $typeName );
$shared = 'shared' . ucfirst( $typeName );
return array( $owned, $shared );
}
/**
* Determines whether the bean has an own list based on
* schema inspection from realtime schema or cache.
*
* @param string $type bean type to get list for
* @param string $target type of list you want to detect
*
* @return boolean
*/
protected function hasOwnList( $type, $target )
{
return isset( $this->columns[$target][$type . '_id'] );
}
/**
* Determines whether the bea has a shared list based on
* schema inspection from realtime schema or cache.
*
* @param string $type bean type to get list for
* @param string $target type of list you are looking for
*
* @return boolean
*/
protected function hasSharedList( $type, $target )
{
return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
}
/**
* @see DuplicationManager::dup
*
* @param OODBBean $bean bean to be copied
* @param array $trail trail to prevent infinite loops
* @param boolean $preserveIDs preserve IDs
*
* @return OODBBean
*/
protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
{
if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
$type = $bean->getMeta( 'type' );
$copy = $this->createCopy( $bean );
foreach ( $this->tables as $table ) {
if ( !empty( $this->filters ) ) {
if ( !in_array( $table, $this->filters ) ) continue;
}
list( $owned, $shared ) = $this->getListNames( $table );
if ( $this->hasSharedList( $type, $table ) ) {
if ( $beans = $bean->$shared ) {
$this->copySharedBeans( $copy, $shared, $beans );
}
} elseif ( $this->hasOwnList( $type, $table ) ) {
if ( $beans = $bean->$owned ) {
$this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
}
$copy->setMeta( 'sys.shadow.' . $owned, NULL );
}
$copy->setMeta( 'sys.shadow.' . $shared, NULL );
}
$copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
return $copy;
}
/**
* Constructor,
* creates a new instance of DupManager.
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
$this->redbean = $toolbox->getRedBean();
$this->associationManager = $this->redbean->getAssociationManager();
}
/**
* Recursively turns the keys of an array into
* camelCase.
*
* @param array $array array to camelize
* @param boolean $dolphinMode whether you want the exception for IDs.
*
* @return array
*/
public function camelfy( $array, $dolphinMode = FALSE ) {
$newArray = array();
foreach( $array as $key => $element ) {
$newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
return strtoupper( $matches[1] );
}, $key);
if ( $dolphinMode ) {
$newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
}
$newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
}
return $newArray;
}
/**
* For better performance you can pass the tables in an array to this method.
* If the tables are available the duplication manager will not query them so
* this might be beneficial for performance.
*
* This method allows two array formats:
*
* <code>
* array( TABLE1, TABLE2 ... )
* </code>
*
* or
*
* <code>
* array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
* </code>
*
* @param array $tables a table cache array
*
* @return void
*/
public function setTables( $tables )
{
foreach ( $tables as $key => $value ) {
if ( is_numeric( $key ) ) {
$this->tables[] = $value;
} else {
$this->tables[] = $key;
$this->columns[$key] = $value;
}
}
$this->cacheTables = TRUE;
}
/**
* Returns a schema array for cache.
* You can use the return value of this method as a cache,
* store it in RAM or on disk and pass it to setTables later.
*
* @return array
*/
public function getSchema()
{
return $this->columns;
}
/**
* Indicates whether you want the duplication manager to cache the database schema.
* If this flag is set to TRUE the duplication manager will query the database schema
* only once. Otherwise the duplicationmanager will, by default, query the schema
* every time a duplication action is performed (dup()).
*
* @param boolean $yesNo TRUE to use caching, FALSE otherwise
*/
public function setCacheTables( $yesNo )
{
$this->cacheTables = $yesNo;
}
/**
* A filter array is an array with table names.
* By setting a table filter you can make the duplication manager only take into account
* certain bean types. Other bean types will be ignored when exporting or making a
* deep copy. If no filters are set all types will be taking into account, this is
* the default behavior.
*
* @param array $filters list of tables to be filtered
*
* @return void
*/
public function setFilters( $filters )
{
if ( !is_array( $filters ) ) {
$filters = array( $filters );
}
$this->filters = $filters;
}
/**
* Makes a copy of a bean. This method makes a deep copy
* of the bean.The copy will have the following features.
* - All beans in own-lists will be duplicated as well
* - All references to shared beans will be copied but not the shared beans themselves
* - All references to parent objects (_id fields) will be copied but not the parents themselves
* In most cases this is the desired scenario for copying beans.
* This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
* (i.e. one that already has been processed) the ID of the bean will be returned.
* This should not happen though.
*
* Note:
* This function does a reflectional database query so it may be slow.
*
* Note:
* this function actually passes the arguments to a protected function called
* duplicate() that does all the work. This method takes care of creating a clone
* of the bean to avoid the bean getting tainted (triggering saving when storing it).
*
* @param OODBBean $bean bean to be copied
* @param array $trail for internal usage, pass array()
* @param boolean $preserveIDs for internal usage
*
* @return OODBBean
*/
public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
{
if ( !count( $this->tables ) ) {
$this->tables = $this->toolbox->getWriter()->getTables();
}
if ( !count( $this->columns ) ) {
foreach ( $this->tables as $table ) {
$this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
}
}
$rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
if ( !$this->cacheTables ) {
$this->tables = array();
$this->columns = array();
}
return $rs;
}
/**
* Exports a collection of beans recursively.
* This method will export an array of beans in the first argument to a
* set of arrays. This can be used to send JSON or XML representations
* of bean hierarchies to the client.
*
* For every bean in the array this method will export:
*
* - contents of the bean
* - all own bean lists (recursively)
* - all shared beans (but not THEIR own lists)
*
* If the second parameter is set to TRUE the parents of the beans in the
* array will be exported as well (but not THEIR parents).
*
* The third parameter can be used to provide a white-list array
* for filtering. This is an array of strings representing type names,
* only the type names in the filter list will be exported.
*
* The fourth parameter can be used to change the keys of the resulting
* export arrays. The default mode is 'snake case' but this leaves the
* keys as-is, because 'snake' is the default case style used by
* RedBeanPHP in the database. You can set this to 'camel' for
* camel cased keys or 'dolphin' (same as camelcase but id will be
* converted to ID instead of Id).
*
* @param array|OODBBean $beans beans to be exported
* @param boolean $parents also export parents
* @param array $filters only these types (whitelist)
* @param string $caseStyle case style identifier
* @param boolean $meta export meta data as well
*
* @return array
*/
public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake', $meta = FALSE)
{
$array = array();
if ( !is_array( $beans ) ) {
$beans = array( $beans );
}
$this->copyMeta = $meta;
foreach ( $beans as $bean ) {
$this->setFilters( $filters );
$duplicate = $this->dup( $bean, array(), TRUE );
$array[] = $duplicate->export( $meta, $parents, FALSE, $filters );
}
if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, TRUE );
return $array;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,560 @@
<?php
namespace RedBeanPHP;
/**
* RedBeanPHP Finder.
* Service class to find beans. For the most part this class
* offers user friendly utility methods for interacting with the
* OODB::find() method, which is rather complex. This class can be
* used to find beans using plain old SQL queries.
*
* @file RedBeanPHP/Finder.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Finder
{
/**
* @var ToolBox
*/
protected $toolbox;
/**
* @var OODB
*/
protected $redbean;
/**
* Constructor.
* The Finder requires a toolbox.
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
$this->redbean = $toolbox->getRedBean();
}
/**
* A custom record-to-bean mapping function for findMulti.
*
* Usage:
*
* <code>
* $collection = R::findMulti( 'shop,product,price',
* 'SELECT shop.*, product.*, price.* FROM shop
* LEFT JOIN product ON product.shop_id = shop.id
* LEFT JOIN price ON price.product_id = product.id', [], [
* Finder::map( 'shop', 'product' ),
* Finder::map( 'product', 'price' ),
* ]);
* </code>
*
* @param string $parentName name of the parent bean
* @param string $childName name of the child bean
*
* @return array
*/
public static function map($parentName,$childName) {
return array(
'a' => $parentName,
'b' => $childName,
'matcher' => function( $parent, $child ) use ( $parentName, $childName ) {
$propertyName = 'own' . ucfirst( $childName );
if (!isset($parent[$propertyName])) {
$parent->noLoad()->{$propertyName} = array();
}
$property = "{$parentName}ID";
return ( $child->$property == $parent->id );
},
'do' => function( $parent, $child ) use ( $childName ) {
$list = 'own'.ucfirst( $childName ).'List';
$parent->noLoad()->{$list}[$child->id] = $child;
}
);
}
/**
* A custom record-to-bean mapping function for findMulti.
*
* Usage:
*
* <code>
* $collection = R::findMulti( 'book,book_tag,tag',
* 'SELECT book.*, book_tag.*, tag.* FROM book
* LEFT JOIN book_tag ON book_tag.book_id = book.id
* LEFT JOIN tag ON book_tag.tag_id = tag.id', [], [
* Finder::nmMap( 'book', 'tag' ),
* ]);
* </code>
*
* @param string $parentName name of the parent bean
* @param string $childName name of the child bean
*
* @return array
*/
public static function nmMap( $parentName, $childName )
{
$types = array($parentName, $childName);
sort( $types );
$link = implode( '_', $types );
return array(
'a' => $parentName,
'b' => $childName,
'matcher' => function( $parent, $child, $beans ) use ( $parentName, $childName, $link ) {
$propertyName = 'shared' . ucfirst( $childName );
if (!isset($parent[$propertyName])) {
$parent->noLoad()->{$propertyName} = array();
}
foreach( $beans[$link] as $linkBean ) {
if ( $linkBean["{$parentName}ID"] == $parent->id && $linkBean["{$childName}ID"] == $child->id ) {
return true;
}
}
},
'do' => function( $parent, $child ) use ( $childName ) {
$list = 'shared'.ucfirst( $childName ).'List';
$parent->noLoad()->{$list}[$child->id] = $child;
}
);
}
/**
* Finder::onMap() -> One-to-N mapping.
* A custom record-to-bean mapping function for findMulti.
* Opposite of Finder::map(). Maps child beans to parents.
*
* Usage:
*
* <code>
* $collection = R::findMulti( 'shop,product',
* 'SELECT shop.*, product.* FROM shop
* LEFT JOIN product ON product.shop_id = shop.id',
* [], [
* Finder::onmap( 'product', 'shop' ),
* ]);
* </code>
*
* Can also be used for instance to attach related beans
* in one-go to save some queries:
*
* Given $users that have a country_id:
*
* <code>
* $all = R::findMulti('country',
* R::genSlots( $users,
* 'SELECT country.* FROM country WHERE id IN ( %s )' ),
* array_column( $users, 'country_id' ),
* [Finder::onmap('country', $gebruikers)]
* );
* </code>
*
* For your convenience, an even shorter notation has been added:
*
* $countries = R::loadJoined( $users, 'country' );
*
* @param string $parentName name of the parent bean
* @param string|array $childName name of the child bean
*
* @return array
*/
public static function onMap($parentName,$childNameOrBeans) {
return array(
'a' => $parentName,
'b' => $childNameOrBeans,
'matcher' => array( $parentName, "{$parentName}_id" ),
'do' => 'match'
);
}
/**
* Finds a bean using a type and a where clause (SQL).
* As with most Query tools in RedBean you can provide values to
* be inserted in the SQL statement by populating the value
* array parameter; you can either use the question mark notation
* or the slot-notation (:keyname).
*
* @param string $type type the type of bean you are looking for
* @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return array
*/
public function find( $type, $sql = NULL, $bindings = array() )
{
if ( !is_array( $bindings ) ) {
throw new RedException(
'Expected array, ' . gettype( $bindings ) . ' given.'
);
}
return $this->redbean->find( $type, array(), $sql, $bindings );
}
/**
* Like find() but also exports the beans as an array.
* This method will perform a find-operation. For every bean
* in the result collection this method will call the export() method.
* This method returns an array containing the array representations
* of every bean in the result set.
*
* @see Finder::find
*
* @param string $type type the type of bean you are looking for
* @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return array
*/
public function findAndExport( $type, $sql = NULL, $bindings = array() )
{
$arr = array();
foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
$arr[] = $item->export();
}
return $arr;
}
/**
* Like find() but returns just one bean instead of an array of beans.
* This method will return only the first bean of the array.
* If no beans are found, this method will return NULL.
*
* @see Finder::find
*
* @param string $type type the type of bean you are looking for
* @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return OODBBean|NULL
*/
public function findOne( $type, $sql = NULL, $bindings = array() )
{
$sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
$items = $this->find( $type, $sql, $bindings );
if ( empty($items) ) {
return NULL;
}
return reset( $items );
}
/**
* Like find() but returns the last bean of the result array.
* Opposite of Finder::findLast().
* If no beans are found, this method will return NULL.
*
* @see Finder::find
*
* @param string $type the type of bean you are looking for
* @param string $sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return OODBBean|NULL
*/
public function findLast( $type, $sql = NULL, $bindings = array() )
{
$items = $this->find( $type, $sql, $bindings );
if ( empty($items) ) {
return NULL;
}
return end( $items );
}
/**
* Tries to find beans of a certain type,
* if no beans are found, it dispenses a bean of that type.
* Note that this function always returns an array.
*
* @see Finder::find
*
* @param string $type the type of bean you are looking for
* @param string $sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return array
*/
public function findOrDispense( $type, $sql = NULL, $bindings = array() )
{
$foundBeans = $this->find( $type, $sql, $bindings );
if ( empty( $foundBeans ) ) {
return array( $this->redbean->dispense( $type ) );
} else {
return $foundBeans;
}
}
/**
* Finds a BeanCollection using the repository.
* A bean collection can be used to retrieve one bean at a time using
* cursors - this is useful for processing large datasets. A bean collection
* will not load all beans into memory all at once, just one at a time.
*
* @param string $type the type of bean you are looking for
* @param string $sql SQL query to find the desired bean, starting right after WHERE clause
* @param array $bindings values array of values to be bound to parameters in query
*
* @return BeanCollection
*/
public function findCollection( $type, $sql, $bindings = array() )
{
return $this->redbean->findCollection( $type, $sql, $bindings );
}
/**
* Finds or creates a bean.
* Tries to find a bean with certain properties specified in the second
* parameter ($like). If the bean is found, it will be returned.
* If multiple beans are found, only the first will be returned.
* If no beans match the criteria, a new bean will be dispensed,
* the criteria will be imported as properties and this new bean
* will be stored and returned.
*
* Format of criteria set: property => value
* The criteria set also supports OR-conditions: property => array( value1, orValue2 )
*
* @param string $type type of bean to search for
* @param array $like criteria set describing bean to search for
* @param boolean $hasBeenCreated set to TRUE if bean has been created
*
* @return OODBBean
*/
public function findOrCreate( $type, $like = array(), $sql = '', &$hasBeenCreated = false )
{
$sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
$beans = $this->findLike( $type, $like, $sql );
if ( count( $beans ) ) {
$bean = reset( $beans );
$hasBeenCreated = false;
return $bean;
}
$bean = $this->redbean->dispense( $type );
$bean->import( $like );
$this->redbean->store( $bean );
$hasBeenCreated = true;
return $bean;
}
/**
* Finds beans by its type and a certain criteria set.
*
* Format of criteria set: property => value
* The criteria set also supports OR-conditions: property => array( value1, orValue2 )
*
* If the additional SQL is a condition, this condition will be glued to the rest
* of the query using an AND operator. Note that this is as far as this method
* can go, there is no way to glue additional SQL using an OR-condition.
* This method provides access to an underlying mechanism in the RedBeanPHP architecture
* to find beans using criteria sets. However, please do not use this method
* for complex queries, use plain SQL instead ( the regular find method ) as it is
* more suitable for the job. This method is
* meant for basic search-by-example operations.
*
* @param string $type type of bean to search for
* @param array $conditions criteria set describing the bean to search for
* @param string $sql additional SQL (for sorting)
* @param array $bindings bindings
*
* @return array
*/
public function findLike( $type, $conditions = array(), $sql = '', $bindings = array() )
{
return $this->redbean->find( $type, $conditions, $sql, $bindings );
}
/**
* Returns a hashmap with bean arrays keyed by type using an SQL
* query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
* this method will return movie and review beans.
*
* Example:
*
* <code>
* $stuff = $finder->findMulti('movie,review', '
* SELECT movie.*, review.* FROM movie
* LEFT JOIN review ON review.movie_id = movie.id');
* </code>
*
* After this operation, $stuff will contain an entry 'movie' containing all
* movies and an entry named 'review' containing all reviews (all beans).
* You can also pass bindings.
*
* If you want to re-map your beans, so you can use $movie->ownReviewList without
* having RedBeanPHP executing an SQL query you can use the fourth parameter to
* define a selection of remapping closures.
*
* The remapping argument (optional) should contain an array of arrays.
* Each array in the remapping array should contain the following entries:
*
* <code>
* array(
* 'a' => TYPE A
* 'b' => TYPE B OR BEANS
* 'matcher' =>
* MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
* OR ARRAY
* WITH FIELD on B that should match with FIELD on A
* AND FIELD on A that should match with FIELD on B
* OR TRUE
* TO JUST PERFORM THE DO-FUNCTION ON EVERY A-BEAN
*
* 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
* (ONLY IF MATCHER IS ALSO A FUNCTION)
* )
* </code>
*
* Using this mechanism you can build your own 'preloader' with tiny function
* snippets (and those can be re-used and shared online of course).
*
* Example:
*
* <code>
* array(
* 'a' => 'movie' //define A as movie
* 'b' => 'review' //define B as review
* matcher' => function( $a, $b ) {
* return ( $b->movie_id == $a->id ); //Perform action if review.movie_id equals movie.id
* }
* 'do' => function( $a, $b ) {
* $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
* $a->clearHistory(); //optional, act 'as if these beans have been loaded through ownReviewList'.
* }
* )
* </code>
*
* The Query Template parameter is optional as well but can be used to
* set a different SQL template (sprintf-style) for processing the original query.
*
* @note the SQL query provided IS NOT THE ONE used internally by this function,
* this function will pre-process the query to get all the data required to find the beans.
*
* @note if you use the 'book.*' notation make SURE you're
* selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
* it's actually an SQL-like template SLOT, not real SQL.
*
* @note instead of an SQL query you can pass a result array as well.
*
* @note the performance of this function is poor, if you deal with large number of records
* please use plain SQL instead. This function has been added as a bridge between plain SQL
* and bean oriented approaches but it is really on the edge of both worlds. You can safely
* use this function to load additional records as beans in paginated context, let's say
* 50-250 records. Anything above that will gradually perform worse. RedBeanPHP was never
* intended to replace SQL but offer tooling to integrate SQL with object oriented
* designs. If you have come to this function, you have reached the final border between
* SQL-oriented design and OOP. Anything after this will be just as good as custom mapping
* or plain old database querying. I recommend the latter.
*
* @param string|array $types a list of types (either array or comma separated string)
* @param string|array $sql optional, an SQL query or an array of prefetched records
* @param array $bindings optional, bindings for SQL query
* @param array $remappings optional, an array of remapping arrays
* @param string $queryTemplate optional, query template
*
* @return array
*/
public function findMulti( $types, $sql = NULL, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
{
if ( !is_array( $types ) ) $types = array_map( 'trim', explode( ',', $types ) );
if ( is_null( $sql ) ) {
$beans = array();
foreach( $types as $type ) $beans[$type] = $this->redbean->find( $type );
} else {
if ( !is_array( $sql ) ) {
$writer = $this->toolbox->getWriter();
$adapter = $this->toolbox->getDatabaseAdapter();
//Repair the query, replace book.* with book.id AS book_id etc..
foreach( $types as $type ) {
$regex = "#( (`?{$type}`?)\.\*)#";
if ( preg_match( $regex, $sql, $matches ) ) {
$pattern = $matches[1];
$table = $matches[2];
$newSelectorArray = array();
$columns = $writer->getColumns( $type );
foreach( $columns as $column => $definition ) {
$newSelectorArray[] = sprintf( $queryTemplate, $table, $column, $type, $column );
}
$newSelector = implode( ',', $newSelectorArray );
$sql = str_replace( $pattern, $newSelector, $sql );
}
}
$rows = $adapter->get( $sql, $bindings );
} else {
$rows = $sql;
}
//Gather the bean data from the query results using the prefix
$wannaBeans = array();
foreach( $types as $type ) {
$wannaBeans[$type] = array();
$prefix = "{$type}__";
foreach( $rows as $rowkey=>$row ) {
$wannaBean = array();
foreach( $row as $cell => $value ) {
if ( strpos( $cell, $prefix ) === 0 ) {
$property = substr( $cell, strlen( $prefix ) );
unset( $rows[$rowkey][$cell] );
$wannaBean[$property] = $value;
}
}
if ( !isset( $wannaBean['id'] ) ) continue;
if ( is_null( $wannaBean['id'] ) ) continue;
$wannaBeans[$type][$wannaBean['id']] = $wannaBean;
}
}
//Turn the rows into beans
$beans = array();
foreach( $wannaBeans as $type => $wannabees ) {
$beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
}
}
//Apply additional re-mappings
foreach($remappings as $remapping) {
$a = $remapping['a'];
$b = $remapping['b'];
if (is_array($b)) {
$firstBean = reset($b);
$type = $firstBean->getMeta('type');
$beans[$type] = $b;
$b = $type;
}
$matcher = $remapping['matcher'];
if (is_callable($matcher) || $matcher === TRUE) {
$do = $remapping['do'];
foreach( $beans[$a] as $bean ) {
if ( $matcher === TRUE ) {
$do( $bean, $beans[$b], $beans, $remapping );
continue;
}
foreach( $beans[$b] as $putBean ) {
if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
}
}
} else {
list($field1, $field2) = $matcher;
foreach( $beans[$b] as $key => $bean ) {
$beans[$b][$key]->{$field1} = $beans[$a][$bean->{$field2}];
}
}
}
return $beans;
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Support functions for RedBeanPHP.
* Additional convenience shortcut functions for RedBeanPHP.
*
* @file RedBeanPHP/Functions.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
/**
* Convenience function for ENUM short syntax in queries.
*
* Usage:
*
* <code>
* R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
* </code>
*
* If a function called EID() already exists you'll have to write this
* wrapper yourself ;)
*
* @param string $enumName enum code as you would pass to R::enum()
*
* @return mixed
*/
if (!function_exists('EID')) {
function EID($enumName)
{
return \RedBeanPHP\Facade::enum( $enumName )->id;
}
}
/**
* Prints the result of R::dump() to the screen using
* print_r.
*
* @param mixed $data data to dump
*
* @return void
*/
if ( !function_exists( 'dmp' ) ) {
function dmp( $list )
{
print_r( \RedBeanPHP\Facade::dump( $list ) );
}
}
/**
* Function alias for R::genSlots().
*/
if ( !function_exists( 'genslots' ) ) {
function genslots( $slots, $tpl = NULL )
{
return \RedBeanPHP\Facade::genSlots( $slots, $tpl );
}
}
/**
* Function alias for R::flat().
*/
if ( !function_exists( 'array_flatten' ) ) {
function array_flatten( $array )
{
return \RedBeanPHP\Facade::flat( $array );
}
}

View File

@ -0,0 +1,182 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Label Maker.
* Makes so-called label beans.
* A label is a bean with only an id, type and name property.
* Labels can be used to create simple entities like categories, tags or enums.
* This service class provides convenience methods to deal with this kind of
* beans.
*
* @file RedBeanPHP/LabelMaker.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class LabelMaker
{
/**
* @var ToolBox
*/
protected $toolbox;
/**
* Constructor.
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
}
/**
* A label is a bean with only an id, type and name property.
* This function will dispense beans for all entries in the array. The
* values of the array will be assigned to the name property of each
* individual bean.
*
* <code>
* $people = R::dispenseLabels( 'person', [ 'Santa', 'Claus' ] );
* </code>
*
* @param string $type type of beans you would like to have
* @param array $labels list of labels, names for each bean
*
* @return array
*/
public function dispenseLabels( $type, $labels )
{
$labelBeans = array();
foreach ( $labels as $label ) {
$labelBean = $this->toolbox->getRedBean()->dispense( $type );
$labelBean->name = $label;
$labelBeans[] = $labelBean;
}
return $labelBeans;
}
/**
* Gathers labels from beans. This function loops through the beans,
* collects the value of the name property for each individual bean
* and stores the names in a new array. The array then gets sorted using the
* default sort function of PHP (sort).
*
* Usage:
*
* <code>
* $o1->name = 'hamburger';
* $o2->name = 'pizza';
* implode( ',', R::gatherLabels( [ $o1, $o2 ] ) ); //hamburger,pizza
* </code>
*
* Note that the return value is an array of strings, not beans.
*
* @param array $beans list of beans to loop through
*
* @return array
*/
public function gatherLabels( $beans )
{
$labels = array();
foreach ( $beans as $bean ) {
$labels[] = $bean->name;
}
sort( $labels );
return $labels;
}
/**
* Fetches an ENUM from the database and creates it if necessary.
* An ENUM has the following format:
*
* <code>
* ENUM:VALUE
* </code>
*
* If you pass 'ENUM' only, this method will return an array of its
* values:
*
* <code>
* implode( ',', R::gatherLabels( R::enum( 'flavour' ) ) ) //'BANANA,MOCCA'
* </code>
*
* If you pass 'ENUM:VALUE' this method will return the specified enum bean
* and create it in the database if it does not exist yet:
*
* <code>
* $bananaFlavour = R::enum( 'flavour:banana' );
* $bananaFlavour->name;
* </code>
*
* So you can use this method to set an ENUM value in a bean:
*
* <code>
* $shake->flavour = R::enum( 'flavour:banana' );
* </code>
*
* the property flavour now contains the enum bean, a parent bean.
* In the database, flavour_id will point to the flavour record with name 'banana'.
*
* @param string $enum ENUM specification for label
*
* @return array|OODBBean
*/
public function enum( $enum )
{
$oodb = $this->toolbox->getRedBean();
if ( strpos( $enum, ':' ) === FALSE ) {
$type = $enum;
$value = FALSE;
} else {
list( $type, $value ) = explode( ':', $enum );
$value = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
}
/**
* We use simply find here, we could use inspect() in fluid mode etc,
* but this would be useless. At first sight it looks clean, you could even
* bake this into find(), however, find not only has to deal with the primary
* search type, people can also include references in the SQL part, so avoiding
* find failures does not matter, this is still the quickest way making use
* of existing functionality.
*
* @note There seems to be a bug in XDebug v2.3.2 causing suppressed
* exceptions like these to surface anyway, to prevent this use:
*
* "xdebug.default_enable = 0"
*
* Also see Github Issue #464
*/
$values = $oodb->find( $type );
if ( $value === FALSE ) {
return $values;
}
foreach( $values as $enumItem ) {
if ( $enumItem->name === $value ) return $enumItem;
}
$newEnumItems = $this->dispenseLabels( $type, array( $value ) );
$newEnumItem = reset( $newEnumItems );
$oodb->store( $newEnumItem );
return $newEnumItem;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace RedBeanPHP;
/**
* RedBean Logging interface.
* Provides a uniform and convenient logging
* interface throughout RedBeanPHP.
*
* @file RedBean/Logging.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Logger
{
/**
* A logger (for PDO or OCI driver) needs to implement the log method.
* The log method will receive logging data. Note that the number of parameters is 0, this means
* all parameters are optional and the number may vary. This way the logger can be used in a very
* flexible way. Sometimes the logger is used to log a simple error message and in other
* situations sql and bindings are passed.
* The log method should be able to accept all kinds of parameters and data by using
* functions like func_num_args/func_get_args.
*
* @param string $message, ...
*
* @return void
*/
public function log();
}

View File

@ -0,0 +1,133 @@
<?php
namespace RedBeanPHP\Logger;
use RedBeanPHP\Logger as Logger;
use RedBeanPHP\RedException as RedException;
/**
* Logger. Provides a basic logging function for RedBeanPHP.
*
* @file RedBeanPHP/Logger.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class RDefault implements Logger
{
/**
* Logger modes
*/
const C_LOGGER_ECHO = 0;
const C_LOGGER_ARRAY = 1;
/**
* @var integer
*/
protected $mode = 0;
/**
* @var array
*/
protected $logs = array();
/**
* Default logger method logging to STDOUT.
* This is the default/reference implementation of a logger.
* This method will write the message value to STDOUT (screen) unless
* you have changed the mode of operation to C_LOGGER_ARRAY.
*
* @param $message (optional) message to log (might also be data or output)
*
* @return void
*/
public function log()
{
if ( func_num_args() < 1 ) return;
foreach ( func_get_args() as $argument ) {
if ( is_array( $argument ) ) {
$log = var_export( $argument, TRUE );
if ( $this->mode === self::C_LOGGER_ECHO ) {
echo $log;
} else {
$this->logs[] = $log;
}
} else {
if ( $this->mode === self::C_LOGGER_ECHO ) {
echo $argument;
} else {
$this->logs[] = $argument;
}
}
if ( $this->mode === self::C_LOGGER_ECHO ) echo "<br>" . PHP_EOL;
}
}
/**
* Returns the internal log array.
* The internal log array is where all log messages are stored.
*
* @return array
*/
public function getLogs()
{
return $this->logs;
}
/**
* Clears the internal log array, removing all
* previously stored entries.
*
* @return self
*/
public function clear()
{
$this->logs = array();
return $this;
}
/**
* Selects a logging mode.
* There are several options available.
*
* * C_LOGGER_ARRAY - log silently, stores entries in internal log array only
* * C_LOGGER_ECHO - also forward log messages directly to STDOUT
*
* @param integer $mode mode of operation for logging object
*
* @return self
*/
public function setMode( $mode )
{
if ($mode !== self::C_LOGGER_ARRAY && $mode !== self::C_LOGGER_ECHO ) {
throw new RedException( 'Invalid mode selected for logger, use C_LOGGER_ARRAY or C_LOGGER_ECHO.' );
}
$this->mode = $mode;
return $this;
}
/**
* Searches for all log entries in internal log array
* for $needle and returns those entries.
* This method will return an array containing all matches for your
* search query.
*
* @param string $needle phrase to look for in internal log array
*
* @return array
*/
public function grep( $needle )
{
$found = array();
foreach( $this->logs as $logEntry ) {
if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry;
}
return $found;
}
}

View File

@ -0,0 +1,270 @@
<?php
namespace RedBeanPHP\Logger\RDefault;
use RedBeanPHP\Logger as Logger;
use RedBeanPHP\Logger\RDefault as RDefault;
use RedBeanPHP\RedException as RedException;
/**
* Debug logger.
* A special logger for debugging purposes.
* Provides debugging logging functions for RedBeanPHP.
*
* @file RedBeanPHP/Logger/RDefault/Debug.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Debug extends RDefault implements Logger
{
/**
* @var integer
*/
protected $strLen = 40;
/**
* @var boolean
*/
protected static $noCLI = FALSE;
/**
* @var boolean
*/
protected $flagUseStringOnlyBinding = FALSE;
/**
* Toggles CLI override. By default debugging functions will
* output differently based on PHP_SAPI values. This function
* allows you to override the PHP_SAPI setting. If you set
* this to TRUE, CLI output will be supressed in favour of
* HTML output. So, to get HTML on the command line use
* setOverrideCLIOutput( TRUE ).
*
* @param boolean $yesNo CLI-override setting flag
*
* @return void
*/
public static function setOverrideCLIOutput( $yesNo )
{
self::$noCLI = $yesNo;
}
/**
* Writes a query for logging with all bindings / params filled
* in.
*
* @param string $newSql the query
* @param array $newBindings the bindings to process (key-value pairs)
*
* @return string
*/
protected function writeQuery( $newSql, $newBindings )
{
//avoid str_replace collisions: slot1 and slot10 (issue 407).
uksort( $newBindings, function( $a, $b ) {
return ( strlen( $b ) - strlen( $a ) );
} );
$newStr = $newSql;
foreach( $newBindings as $slot => $value ) {
if ( strpos( $slot, ':' ) === 0 ) {
$newStr = str_replace( $slot, $this->fillInValue( $value ), $newStr );
}
}
return $newStr;
}
/**
* Fills in a value of a binding and truncates the
* resulting string if necessary.
*
* @param mixed $value bound value
*
* @return string
*/
protected function fillInValue( $value )
{
if ( is_array( $value ) && count( $value ) == 2 ) {
$paramType = end( $value );
$value = reset( $value );
} else {
$paramType = NULL;
}
if ( is_null( $value ) ) $value = 'NULL';
if ( $this->flagUseStringOnlyBinding ) $paramType = \PDO::PARAM_STR;
if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) {
if ( \RedBeanPHP\QueryWriter\AQueryWriter::canBeTreatedAsInt( $value ) || $value === 'NULL') {
$paramType = \PDO::PARAM_INT;
} else {
$paramType = \PDO::PARAM_STR;
}
}
if ( strlen( $value ) > ( $this->strLen ) ) {
$value = substr( $value, 0, ( $this->strLen ) ).'... ';
}
if ($paramType === \PDO::PARAM_STR) {
$value = '\''.$value.'\'';
}
return $value;
}
/**
* Dependending on the current mode of operation,
* this method will either log and output to STDIN or
* just log.
*
* Depending on the value of constant PHP_SAPI this function
* will format output for console or HTML.
*
* @param string $str string to log or output and log
*
* @return void
*/
protected function output( $str )
{
$this->logs[] = $str;
if ( !$this->mode ) {
$highlight = FALSE;
/* just a quick heuritsic to highlight schema changes */
if ( strpos( $str, 'CREATE' ) === 0
|| strpos( $str, 'ALTER' ) === 0
|| strpos( $str, 'DROP' ) === 0) {
$highlight = TRUE;
}
if (PHP_SAPI === 'cli' && !self::$noCLI) {
if ($highlight) echo "\e[91m";
echo $str, PHP_EOL;
echo "\e[39m";
} else {
if ($highlight) {
echo "<b style=\"color:red\">{$str}</b>";
} else {
echo $str;
}
echo '<br />';
}
}
}
/**
* Normalizes the slots in an SQL string.
* Replaces question mark slots with :slot1 :slot2 etc.
*
* @param string $sql sql to normalize
*
* @return string
*/
protected function normalizeSlots( $sql )
{
$newSql = $sql;
$i = 0;
while(strpos($newSql, '?') !== FALSE ){
$pos = strpos( $newSql, '?' );
$slot = ':slot'.$i;
$begin = substr( $newSql, 0, $pos );
$end = substr( $newSql, $pos+1 );
if (PHP_SAPI === 'cli' && !self::$noCLI) {
$newSql = "{$begin}\e[32m{$slot}\e[39m{$end}";
} else {
$newSql = "{$begin}<b style=\"color:green\">$slot</b>{$end}";
}
$i ++;
}
return $newSql;
}
/**
* Normalizes the bindings.
* Replaces numeric binding keys with :slot1 :slot2 etc.
*
* @param array $bindings bindings to normalize
*
* @return array
*/
protected function normalizeBindings( $bindings )
{
$i = 0;
$newBindings = array();
foreach( $bindings as $key => $value ) {
if ( is_numeric($key) ) {
$newKey = ':slot'.$i;
$newBindings[$newKey] = $value;
$i++;
} else {
$newBindings[$key] = $value;
}
}
return $newBindings;
}
/**
* Logger method.
*
* Takes a number of arguments tries to create
* a proper debug log based on the available data.
*
* @return void
*/
public function log()
{
if ( func_num_args() < 1 ) return;
$sql = func_get_arg( 0 );
if ( func_num_args() < 2) {
$bindings = array();
} else {
$bindings = func_get_arg( 1 );
}
if ( !is_array( $bindings ) ) {
return $this->output( $sql );
}
$newSql = $this->normalizeSlots( $sql );
$newBindings = $this->normalizeBindings( $bindings );
$newStr = $this->writeQuery( $newSql, $newBindings );
$this->output( $newStr );
}
/**
* Sets the max string length for the parameter output in
* SQL queries. Set this value to a reasonable number to
* keep you SQL queries readable.
*
* @param integer $len string length
*
* @return self
*/
public function setParamStringLength( $len = 20 )
{
$this->strLen = max(0, $len);
return $this;
}
/**
* Whether to bind all parameters as strings.
* If set to TRUE this will cause all integers to be bound as STRINGS.
* This will NOT affect NULL values.
*
* @param boolean $yesNo pass TRUE to bind all parameters as strings.
*
* @return self
*/
public function setUseStringOnlyBinding( $yesNo = false )
{
$this->flagUseStringOnlyBinding = (boolean) $yesNo;
return $this;
}
}

View File

@ -0,0 +1,596 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\Repository as Repository;
use RedBeanPHP\Repository\Fluid as FluidRepo;
use RedBeanPHP\Repository\Frozen as FrozenRepo;
/**
* RedBean Object Oriented DataBase.
*
* The RedBean OODB Class is the main class of RedBeanPHP.
* It takes OODBBean objects and stores them to and loads them from the
* database as well as providing other CRUD functions. This class acts as a
* object database.
*
* @file RedBeanPHP/OODB.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class OODB extends Observable
{
/**
* @var array
*/
private static $sqlFilters = array();
/**
* @var array
*/
protected $chillList = array();
/**
* @var array
*/
protected $stash = NULL;
/*
* @var integer
*/
protected $nesting = 0;
/**
* @var DBAdapter
*/
protected $writer;
/**
* @var boolean
*/
protected $isFrozen = FALSE;
/**
* @var FacadeBeanHelper
*/
protected $beanhelper = NULL;
/**
* @var AssociationManager
*/
protected $assocManager = NULL;
/**
* @var Repository
*/
protected $repository = NULL;
/**
* @var FrozenRepo
*/
protected $frozenRepository = NULL;
/**
* @var FluidRepo
*/
protected $fluidRepository = NULL;
/**
* @var boolean
*/
protected static $autoClearHistoryAfterStore = FALSE;
/**
* If set to TRUE, this method will call clearHistory every time
* the bean gets stored.
*
* @param boolean $autoClear auto clear option
*
* @return void
*/
public static function autoClearHistoryAfterStore( $autoClear = TRUE )
{
self::$autoClearHistoryAfterStore = (boolean) $autoClear;
}
/**
* Unboxes a bean from a FUSE model if needed and checks whether the bean is
* an instance of OODBBean.
*
* @param OODBBean $bean bean you wish to unbox
*
* @return OODBBean
*/
protected function unboxIfNeeded( $bean )
{
if ( $bean instanceof SimpleModel ) {
$bean = $bean->unbox();
}
if ( !( $bean instanceof OODBBean ) ) {
throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
}
return $bean;
}
/**
* Constructor, requires a query writer.
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param QueryWriter $writer writer
* @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
*/
public function __construct( QueryWriter $writer, $frozen = FALSE )
{
if ( $writer instanceof QueryWriter ) {
$this->writer = $writer;
}
$this->freeze( $frozen );
}
/**
* Toggles fluid or frozen mode. In fluid mode the database
* structure is adjusted to accomodate your objects. In frozen mode
* this is not the case.
*
* You can also pass an array containing a selection of frozen types.
* Let's call this chilly mode, it's just like fluid mode except that
* certain types (i.e. tables) aren't touched.
*
* @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
*
* @return void
*/
public function freeze( $toggle )
{
if ( is_array( $toggle ) ) {
$this->chillList = $toggle;
$this->isFrozen = FALSE;
} else {
$this->isFrozen = (boolean) $toggle;
}
if ( $this->isFrozen ) {
if ( !$this->frozenRepository ) {
$this->frozenRepository = new FrozenRepo( $this, $this->writer );
}
$this->repository = $this->frozenRepository;
} else {
if ( !$this->fluidRepository ) {
$this->fluidRepository = new FluidRepo( $this, $this->writer );
}
$this->repository = $this->fluidRepository;
}
if ( count( self::$sqlFilters ) ) {
AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
}
}
/**
* Returns the current mode of operation of RedBean.
* In fluid mode the database
* structure is adjusted to accomodate your objects.
* In frozen mode
* this is not the case.
*
* @return boolean
*/
public function isFrozen()
{
return (bool) $this->isFrozen;
}
/**
* Determines whether a type is in the chill list.
* If a type is 'chilled' it's frozen, so its schema cannot be
* changed anymore. However other bean types may still be modified.
* This method is a convenience method for other objects to check if
* the schema of a certain type is locked for modification.
*
* @param string $type the type you wish to check
*
* @return boolean
*/
public function isChilled( $type )
{
return (boolean) ( in_array( $type, $this->chillList ) );
}
/**
* Dispenses a new bean (a OODBBean Bean Object)
* of the specified type. Always
* use this function to get an empty bean object. Never
* instantiate a OODBBean yourself because it needs
* to be configured before you can use it with RedBean. This
* function applies the appropriate initialization /
* configuration for you.
*
* @param string $type type of bean you want to dispense
* @param string $number number of beans you would like to get
* @param boolean $alwaysReturnArray if TRUE always returns the result as an array
*
* @return OODBBean
*/
public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
{
if ( $number < 1 ) {
if ( $alwaysReturnArray ) return array();
return NULL;
}
return $this->repository->dispense( $type, $number, $alwaysReturnArray );
}
/**
* Sets bean helper to be given to beans.
* Bean helpers assist beans in getting a reference to a toolbox.
*
* @param BeanHelper $beanhelper helper
*
* @return void
*/
public function setBeanHelper( BeanHelper $beanhelper )
{
$this->beanhelper = $beanhelper;
}
/**
* Returns the current bean helper.
* Bean helpers assist beans in getting a reference to a toolbox.
*
* @return BeanHelper
*/
public function getBeanHelper()
{
return $this->beanhelper;
}
/**
* Checks whether a OODBBean bean is valid.
* If the type is not valid or the ID is not valid it will
* throw an exception: Security.
*
* @param OODBBean $bean the bean that needs to be checked
*
* @return void
*/
public function check( OODBBean $bean )
{
$this->repository->check( $bean );
}
/**
* Searches the database for a bean that matches conditions $conditions and sql $addSQL
* and returns an array containing all the beans that have been found.
*
* Conditions need to take form:
*
* <code>
* array(
* 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
* 'PROPERTY' => array( POSSIBLE VALUES... )
* );
* </code>
*
* All conditions are glued together using the AND-operator, while all value lists
* are glued using IN-operators thus acting as OR-conditions.
*
* Note that you can use property names; the columns will be extracted using the
* appropriate bean formatter.
*
* @param string $type type of beans you are looking for
* @param array $conditions list of conditions
* @param string $sql SQL to be used in query
* @param array $bindings a list of values to bind to query parameters
*
* @return array
*/
public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
{
return $this->repository->find( $type, $conditions, $sql, $bindings );
}
/**
* Same as find() but returns a BeanCollection.
*
* @param string $type type of beans you are looking for
* @param string $sql SQL to be used in query
* @param array $bindings a list of values to bind to query parameters
*
* @return BeanCollection
*/
public function findCollection( $type, $sql = NULL, $bindings = array() )
{
return $this->repository->findCollection( $type, $sql, $bindings );
}
/**
* Checks whether the specified table already exists in the database.
* Not part of the Object Database interface!
*
* @deprecated Use AQueryWriter::typeExists() instead.
*
* @param string $table table name
*
* @return boolean
*/
public function tableExists( $table )
{
return $this->repository->tableExists( $table );
}
/**
* Stores a bean in the database. This method takes a
* OODBBean Bean Object $bean and stores it
* in the database. If the database schema is not compatible
* with this bean and RedBean runs in fluid mode the schema
* will be altered to store the bean correctly.
* If the database schema is not compatible with this bean and
* RedBean runs in frozen mode it will throw an exception.
* This function returns the primary key ID of the inserted
* bean.
*
* The return value is an integer if possible. If it is not possible to
* represent the value as an integer a string will be returned. We use
* explicit casts instead of functions to preserve performance
* (0.13 vs 0.28 for 10000 iterations on Core i3).
*
* @param OODBBean|SimpleModel $bean bean to store
*
* @return integer|string
*/
public function store( $bean )
{
$bean = $this->unboxIfNeeded( $bean );
$id = $this->repository->store( $bean );
if ( self::$autoClearHistoryAfterStore ) {
$bean->clearHistory();
}
return $id;
}
/**
* Loads a bean from the object database.
* It searches for a OODBBean Bean Object in the
* database. It does not matter how this bean has been stored.
* RedBean uses the primary key ID $id and the string $type
* to find the bean. The $type specifies what kind of bean you
* are looking for; this is the same type as used with the
* dispense() function. If RedBean finds the bean it will return
* the OODB Bean object; if it cannot find the bean
* RedBean will return a new bean of type $type and with
* primary key ID 0. In the latter case it acts basically the
* same as dispense().
*
* Important note:
* If the bean cannot be found in the database a new bean of
* the specified type will be generated and returned.
*
* @param string $type type of bean you want to load
* @param integer $id ID of the bean you want to load
*
* @return OODBBean
*/
public function load( $type, $id )
{
return $this->repository->load( $type, $id );
}
/**
* Removes a bean from the database.
* This function will remove the specified OODBBean
* Bean Object from the database.
*
* @param OODBBean|SimpleModel $bean bean you want to remove from database
*
* @return void
*/
public function trash( $bean )
{
$bean = $this->unboxIfNeeded( $bean );
return $this->repository->trash( $bean );
}
/**
* Returns an array of beans. Pass a type and a series of ids and
* this method will bring you the corresponding beans.
*
* important note: Because this method loads beans using the load()
* function (but faster) it will return empty beans with ID 0 for
* every bean that could not be located. The resulting beans will have the
* passed IDs as their keys.
*
* @param string $type type of beans
* @param array $ids ids to load
*
* @return array
*/
public function batch( $type, $ids )
{
return $this->repository->batch( $type, $ids );
}
/**
* This is a convenience method; it converts database rows
* (arrays) into beans. Given a type and a set of rows this method
* will return an array of beans of the specified type loaded with
* the data fields provided by the result set from the database.
*
* @param string $type type of beans you would like to have
* @param array $rows rows from the database result
* @param string $mask mask to apply for meta data
*
* @return array
*/
public function convertToBeans( $type, $rows, $mask = NULL )
{
return $this->repository->convertToBeans( $type, $rows, $mask );
}
/**
* Counts the number of beans of type $type.
* This method accepts a second argument to modify the count-query.
* A third argument can be used to provide bindings for the SQL snippet.
*
* @param string $type type of bean we are looking for
* @param string $addSQL additional SQL snippet
* @param array $bindings parameters to bind to SQL
*
* @return integer
*/
public function count( $type, $addSQL = '', $bindings = array() )
{
return $this->repository->count( $type, $addSQL, $bindings );
}
/**
* Trash all beans of a given type. Wipes an entire type of bean.
*
* @param string $type type of bean you wish to delete all instances of
*
* @return boolean
*/
public function wipe( $type )
{
return $this->repository->wipe( $type );
}
/**
* Returns an Association Manager for use with OODB.
* A simple getter function to obtain a reference to the association manager used for
* storage and more.
*
* @return AssociationManager
*/
public function getAssociationManager()
{
if ( !isset( $this->assocManager ) ) {
throw new RedException( 'No association manager available.' );
}
return $this->assocManager;
}
/**
* Sets the association manager instance to be used by this OODB.
* A simple setter function to set the association manager to be used for storage and
* more.
*
* @param AssociationManager $assocManager sets the association manager to be used
*
* @return void
*/
public function setAssociationManager( AssociationManager $assocManager )
{
$this->assocManager = $assocManager;
}
/**
* Returns the currently used repository instance.
* For testing purposes only.
*
* @return Repository
*/
public function getCurrentRepository()
{
return $this->repository;
}
/**
* Clears all function bindings.
*
* @return void
*/
public function clearAllFuncBindings()
{
self::$sqlFilters = array();
AQueryWriter::setSQLFilters( self::$sqlFilters, FALSE );
}
/**
* Binds an SQL function to a column.
* This method can be used to setup a decode/encode scheme or
* perform UUID insertion. This method is especially useful for handling
* MySQL spatial columns, because they need to be processed first using
* the asText/GeomFromText functions.
*
* @param string $mode mode to set function for, i.e. read or write
* @param string $field field (table.column) to bind SQL function to
* @param string $function SQL function to bind to field
* @param boolean $isTemplate TRUE if $function is an SQL string, FALSE for just a function name
*
* @return void
*/
public function bindFunc( $mode, $field, $function, $isTemplate = FALSE )
{
list( $type, $property ) = explode( '.', $field );
$mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
if ( is_null( $function ) ) {
unset( self::$sqlFilters[$mode][$type][$property] );
} else {
if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
if ($isTemplate) {
$code = sprintf( $function, '?' );
} else {
$code = "{$function}(?)";
}
self::$sqlFilters[$mode][$type][$property] = $code;
} else {
if ($isTemplate) {
$code = sprintf( $function, $field );
} else {
$code = "{$function}({$field})";
}
self::$sqlFilters[$mode][$type][$property] = $code;
}
}
AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Observer as Observer;
/**
* Observable
* Base class for Observables
*
* @file RedBeanPHP/Observable.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
/**
* @var array
*/
private $observers = array();
/**
* Implementation of the Observer Pattern.
* Adds an event listener to the observable object.
* First argument should be the name of the event you wish to listen for.
* Second argument should be the object that wants to be notified in case
* the event occurs.
*
* @param string $eventname event identifier
* @param Observer $observer observer instance
*
* @return void
*/
public function addEventListener( $eventname, Observer $observer )
{
if ( !isset( $this->observers[$eventname] ) ) {
$this->observers[$eventname] = array();
}
if ( in_array( $observer, $this->observers[$eventname] ) ) {
return;
}
$this->observers[$eventname][] = $observer;
}
/**
* Notifies listeners.
* Sends the signal $eventname, the event identifier and a message object
* to all observers that have been registered to receive notification for
* this event. Part of the observer pattern implementation in RedBeanPHP.
*
* @param string $eventname event you want signal
* @param mixed $info message object to send along
*
* @return void
*/
public function signal( $eventname, $info )
{
if ( !isset( $this->observers[$eventname] ) ) {
$this->observers[$eventname] = array();
}
foreach ( $this->observers[$eventname] as $observer ) {
$observer->onEvent( $eventname, $info );
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace RedBeanPHP;
/**
* Observer.
*
* Interface for Observer object. Implementation of the
* observer pattern.
*
* @file RedBeanPHP/Observer.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
* @desc Part of the observer pattern in RedBean
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Observer
{
/**
* An observer object needs to be capable of receiving
* notifications. Therefore the observer needs to implement the
* onEvent method with two parameters: the event identifier specifying the
* current event and a message object (in RedBeanPHP this can also be a bean).
*
* @param string $eventname event identifier
* @param mixed $bean a message sent along with the notification
*
* @return void
*/
public function onEvent( $eventname, $bean );
}

View File

@ -0,0 +1,26 @@
<?php
namespace RedBeanPHP;
/**
* RedBean Plugin.
* Marker interface for plugins.
* Use this interface when defining new plugins, it's an
* easy way for the rest of the application to recognize your
* plugin. This plugin interface does not require you to
* implement a specific API.
*
* @file RedBean/Plugin.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface Plugin
{
}
;

View File

@ -0,0 +1,246 @@
<?php
namespace RedBeanPHP\Plugin;
use RedBeanPHP\ToolBox;
use RedBeanPHP\OODBBean;
/**
* @experimental
*
* PoolDB
*
* Experimental plugin that makes bean automatically connect
* to the database they come from.
*/
/**
* NonStaticBeanHelper
*
* The NonStaticBeanHelper is used by the database pool class PoolDB.
*/
class NonStaticBeanHelper extends RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper {
/**
* Returns the extracted toolbox.
*
* @return ToolBox
*/
public function getExtractedToolbox()
{
$toolbox = $this->toolbox;
return array( $toolbox->getRedbean(), $toolbox->getDatabaseAdapter(), $toolbox->getWriter(), $toolbox );
}
/**
* Constructor
*
* Creates a new instance of the NonStaticBeanHelper.
* The NonStaticBeanHelper is used by the database pool class PoolDB.
*/
public function __construct($toolbox)
{
$this->toolbox = $toolbox;
}
}
/**
* PoolDB
*
* Represents a pool of databases that will have persisting
* associations with the beans they dispense. Saving a bean from
* a pooled database will make sure that the bean will be stored
* in the database it originated from instead of the currently
* selected database.
*
* @experimental
* This is an experimental plugin, added for testing purposes
* only.
*
* Usage:
*
* <code>
* // Let's add some databases
* R::addPoolDatabase( 'db1', 'sqlite:/tmp/db1.txt' );
* R::nuke();
* R::addPoolDatabase( 'db2', 'sqlite:/tmp/db2.txt' );
* R::nuke();
* R::addPoolDatabase( 'db3', 'sqlite:/tmp/db3.txt' );
* R::nuke();
*
* // create a book and page in db1
* R::selectDatabase('db1');
* $book = R::dispense(array(
* '_type' => 'book',
* 'title' => 'Databases for Beans',
* 'ownPageList' => array(
* 0 => array(
* '_type' => 'page',
* 'content' => 'Lorem Ipsum'
* )
* )
* ));
* R::store($book);
*
* //switch to db2
* R::selectDatabase( 'db2' );
* //obtain pages (from db1)
* $pages = count($book->ownPageList);
* echo "I found {$pages} pages in db1.\n";
*
* $pages = R::count('page');
* echo "There are {$pages} pages in db2.\n";
*
* // create pizza in db 2
* $pizza = R::dispense('pizza');
*
* // switch to db3
* R::selectDatabase( 'db3' );
*
* // store pizza in db2
* $pizza->pepperoni = true;
* R::store($pizza);
*
* $pizzas = R::count('pizza');
* echo "There are {$pizzas} in pizzas db3.\n";
* R::selectDatabase('db2');
* $pizzas = R::count('pizza');
* echo "There are {$pizzas} pizzas in db2.\n";
* </code>
*
* @file RedBeanPHP/Plugin/Pool.php
* @author RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class PoolDB extends \RedBeanPHP\OODB {
/**
* @var array
*/
private static $pool = array();
/**
* @var ToolBox
*/
private $toolbox;
/**
* @var OODB
*/
private $oodb;
/**
* @var string
*/
private $key;
/**
* @var NonStaticBeanHelper
*/
private $beanHelper;
/**
* Constructor
*
* creates a new instance of the database pool.
*
* @param string $key key
* @param OODB $oodb oodb instance
*/
public function __construct( $key, $oodb)
{
self::$pool[$key] = $oodb;
$this->oodb = $oodb;
$this->key = $key;
parent::__construct( $oodb->writer, $oodb->isFrozen );
}
/**
* Sets the toolbox to be used by the database pool.
*
* @param ToolBox $toolbox toolbox
*
* @return void
*/
public function setToolBox( $toolbox )
{
$this->toolbox = $toolbox;
$this->beanHelper = new NonStaticBeanHelper( $this->toolbox );
$this->beanHelper->key = $this->key;
$this->oodb->setBeanHelper( $this->beanHelper );
}
/**
* Returns the bean helper of the database pool.
*
* @return BeanHelper
*/
public function getBeanHelper()
{
return $this->beanHelper;
}
/**
* Implements the find operation.
*
* @see OODB::find
*/
public function find( $type, $conditions=array(), $sql=NULL, $bindings=array())
{
return parent::find($type, $conditions, $sql, $bindings);
}
/**
* Dispenses a new bean from the database pool.
* A bean that has been dispensed by the pool will have a special
* meta attribute called sys.source containing the key identifying
* the database in the pool it originated from.
*
* @see OODB::dispense
*/
public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
{
$bean = $this->oodb->dispense( $type, $number, $alwaysReturnArray );
foreach( self::$pool as $key => $db ) {
if ( $this->oodb === $db ) {
$bean->setMeta( 'sys.source',$key );
}
}
return $bean;
}
/**
* Stores the specified bean in the database in the pool
* it originated from by looking up the sys.source attribute.
*
* @see OODB::store
*/
public function store( $bean )
{
$dataSource = $bean->getMeta('sys.source');
if ( !is_null( $dataSource ) ) {
$result = self::$pool[$dataSource]->store( $bean );
} else {
$result = parent::store( $bean );
}
return $result;
}
}
R::ext( 'addPoolDatabase', function( $dbName, $dsn, $user=NULL, $pass=NULL ) {
R::addDatabase( $dbName, $dsn, $user, $pass );
R::selectDatabase( $dbName );
list($oodb, $adapter, $writer, ) = R::getExtractedToolbox();
$poolDB = new PoolDB( $dbName, $oodb );
$toolbox = new ToolBox( $poolDB, $adapter, $writer );
$poolDB->setToolBox( $toolbox );
R::$toolboxes[$dbName]=$toolbox;
R::selectDatabase( $dbName, TRUE );
} );

View File

@ -0,0 +1,86 @@
<?php
/**
* Short Query Notation Library
*
* SQN is a small library that allows you to write
* convention based SQL queries using a short notation.
* SQL is a very flexible and powerful language. Since SQL
* does not rely on assumptions you have to specify a lot.
* The SQN-library uses some basic assumptions regarding
* naming conventions and in return it gives you a shorter notation.
*
* Usage:
*
* R::sqn('shop<product<price'); - left joins shop, product and price
* R::sqn('book<<tag'); - doubly left joins book, and tag using book_tag
*
* SQN assumes id fields follow the following conventions:
*
* Primary key: id
* Foreign key: {table}_id
* No table prefix.
*
* SQN can also generate additional aliases:
*
* R::sqn( ..., 'area/x,y;place/x,y' ) - area_x area_y place_x place_y
*
* @author Gabor de Mooij
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij
* This source file is subject to the BSD License that is bundled
* with this source code in the file license.txt.
*/
R::ext('sqn', function( $query, $aliases = null, $q = '`' ) {
$map = [
'|' => 'INNER JOIN',
'||' => 'INNER JOIN',
'>' => 'RIGHT JOIN',
'>>' => 'RIGHT JOIN',
'<' => 'LEFT JOIN',
'<<' => 'LEFT JOIN',
];
$select = [];
$from = '';
$joins = [];
$prev = '';
$ents = preg_split( '/[^\w_]+/', $query );
$tokens = preg_split( '/[\w_]+/', $query );
array_pop($tokens);
foreach( $ents as $i => $ent ) {
$select[] = " {$q}{$ent}{$q}.* ";
if (!$i) {
$from = $ent;
$prev = $ent;
continue;
}
if ( $tokens[$i] == '<' || $tokens[$i] == '>' || $tokens[$i] == '|') {
$join[] = " {$map[$tokens[$i]]} {$q}{$ent}{$q} ON {$q}{$ent}{$q}.{$prev}_id = {$q}{$prev}{$q}.id ";
}
if ( $tokens[$i] == '<<' || $tokens[$i] == '>>' || $tokens[$i] == '||') {
$combi = [$prev, $ent];
sort( $combi );
$combi = implode( '_', $combi );
$select[] = " {$q}{$combi}{$q}.* ";
$join[] = " {$map[$tokens[$i]]} {$q}{$combi}{$q} ON {$q}{$combi}{$q}.{$prev}_id = {$q}{$prev}{$q}.id ";
$join[] = " {$map[$tokens[$i]]} {$q}{$ent}{$q} ON {$q}{$combi}{$q}.{$ent}_id = {$q}{$ent}{$q}.id ";
}
$prev = $ent;
}
if (!is_null($aliases)) {
$aliases = explode(';', $aliases);
foreach($aliases as $alias) {
list($table, $cols) = explode('/', $alias);
$cols = explode(',', $cols);
foreach($cols as $col) {
$select[] = " {$q}{$table}{$q}.{$q}{$col}{$q} AS {$q}{$table}_{$col}{$q} ";
}
}
}
$selectSQL = implode( ",\n", $select );
$joinSQL = implode( "\n", $join );
return "SELECT{$selectSQL}\n FROM {$q}{$from}{$q}\n{$joinSQL}";
});

View File

@ -0,0 +1,56 @@
<?php
/**
* Tiny Query Builder
*
* <code>
* $sql = build_query([
* [ 'SELECT * FROM book'],
* [$title ,'WHERE','title = ?'],
* [$price ,'AND','price < ?'],
* [$order ,'ORDER BY ? ASC'],
* [$limit ,'LIMIT ?']
* ]);
* </code>
*
* Now, if we have a $title and a $price the query will be:
* 'SELECT * FROM book WHERE title = ? AND price < ? '
* If we only have a $price and a $limit:
* 'SELECT * FROM book WHERE price < ? LIMIT ?'
* The Query Builder works very easy, it simply loops through the array,
* each element is another array inside this main array,
* let's call this inner array a 'piece'.
* A piece can have one, two or three elements.
* If it has one element, the element is simply concatenated to the final query.
* If a piece has two elements, the second element will be
* concatenated only if the first evaluates to TRUE.
* Finally a piece having three elements works the same as a piece with two elements,
* except that it will use the glue provided in the second element
* to concat the value of the third element. The glue acts as a little tube of glue.
* If there is still glue left in the tube (WHERE) it will preserve this
* until it can be applied (so the first AND will be ignored in case of a WHERE condition).
*/
R::ext('buildQuery', function($pieces) {
$sql = '';
$glue = NULL;
foreach( $pieces as $piece ) {
$n = count( $piece );
switch( $n ) {
case 1:
$sql .= " {$piece[0]} ";
break;
case 2:
$glue = NULL;
if (!is_null($piece[0])) $sql .= " {$piece[1]} ";
break;
case 3:
$glue = ( is_null( $glue ) ) ? $piece[1] : $glue;
if (!is_null($piece[0])) {
$sql .= " {$glue} {$piece[2]} ";
$glue = NULL;
}
break;
}
}
return $sql;
});

View File

@ -0,0 +1,2 @@
This folder has been reserved for your plugins

View File

@ -0,0 +1,559 @@
<?php
namespace RedBeanPHP;
/**
* QueryWriter
* Interface for QueryWriters.
* Describes the API for a QueryWriter.
*
* Terminology:
*
* - beautified property (a camelCased property, has to be converted first)
* - beautified type (a camelCased type, has to be converted first)
* - type (a bean type, corresponds directly to a table)
* - property (a bean property, corresponds directly to a column)
* - table (a checked and quoted type, ready for use in a query)
* - column (a checked and quoted property, ready for use in query)
* - tableNoQ (same as type, but in context of a database operation)
* - columnNoQ (same as property, but in context of a database operation)
*
* @file RedBeanPHP/QueryWriter.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
interface QueryWriter
{
/**
* SQL filter constants
*/
const C_SQLFILTER_READ = 'r';
const C_SQLFILTER_WRITE = 'w';
/**
* Query Writer constants.
*/
const C_SQLSTATE_NO_SUCH_TABLE = 1;
const C_SQLSTATE_NO_SUCH_COLUMN = 2;
const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
const C_SQLSTATE_LOCK_TIMEOUT = 4;
/**
* Define data type regions
*
* 00 - 80: normal data types
* 80 - 99: special data types, only scan/code if requested
* 99 : specified by user, don't change
*/
const C_DATATYPE_RANGE_SPECIAL = 80;
const C_DATATYPE_RANGE_SPECIFIED = 99;
/**
* Define GLUE types for use with glueSQLCondition methods.
* Determines how to prefix a snippet of SQL before appending it
* to other SQL (or integrating it, mixing it otherwise).
*
* WHERE - glue as WHERE condition
* AND - glue as AND condition
*/
const C_GLUE_WHERE = 1;
const C_GLUE_AND = 2;
/**
* CTE Select Snippet
* Constants specifying select snippets for CTE queries
*/
const C_CTE_SELECT_NORMAL = FALSE;
const C_CTE_SELECT_COUNT = TRUE;
/**
* Parses an sql string to create joins if needed.
*
* For instance with $type = 'book' and $sql = ' @joined.author.name LIKE ? OR @joined.detail.title LIKE ? '
* parseJoin will return the following SQL:
* ' LEFT JOIN `author` ON `author`.id = `book`.author_id
* LEFT JOIN `detail` ON `detail`.id = `book`.detail_id
* WHERE author.name LIKE ? OR detail.title LIKE ? '
*
* @note this feature requires Narrow Field Mode to be activated (default).
*
* @note A default implementation is available in AQueryWriter
* unless a database uses very different SQL this should suffice.
*
* @param string $type the source type for the join
* @param string $sql the sql string to be parsed
*
* @return string
*/
public function parseJoin( $type, $sql );
/**
* Writes an SQL Snippet for a JOIN, returns the
* SQL snippet string.
*
* @note A default implementation is available in AQueryWriter
* unless a database uses very different SQL this should suffice.
*
* @param string $type source type
* @param string $targetType target type (type to join)
* @param string $leftRight type of join (possible: 'LEFT', 'RIGHT' or 'INNER')
* @param string $joinType relation between joined tables (possible: 'parent', 'own', 'shared')
* @param boolean $firstOfChain is it the join of a chain (or the only join)
* @param string $suffix suffix to add for aliasing tables (for joining same table multiple times)
*
* @return string $joinSQLSnippet
*/
public function writeJoin( $type, $targetType, $leftRight, $joinType, $firstOfChain, $suffix );
/**
* Glues an SQL snippet to the beginning of a WHERE clause.
* This ensures users don't have to add WHERE to their query snippets.
*
* The snippet gets prefixed with WHERE or AND
* if it starts with a condition.
*
* If the snippet does NOT start with a condition (or this function thinks so)
* the snippet is returned as-is.
*
* The GLUE type determines the prefix:
*
* * NONE prefixes with WHERE
* * WHERE prefixes with WHERE and replaces AND if snippets starts with AND
* * AND prefixes with AND
*
* This method will never replace WHERE with AND since a snippet should never
* begin with WHERE in the first place. OR is not supported.
*
* Only a limited set of clauses will be recognized as non-conditions.
* For instance beginning a snippet with complex statements like JOIN or UNION
* will not work. This is too complex for use in a snippet.
*
* @note A default implementation is available in AQueryWriter
* unless a database uses very different SQL this should suffice.
*
* @param string $sql SQL Snippet
* @param integer $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
*
* @return string
*/
public function glueSQLCondition( $sql, $glue = NULL );
/**
* Determines if there is a LIMIT 1 clause in the SQL.
* If not, it will add a LIMIT 1. (used for findOne).
*
* @note A default implementation is available in AQueryWriter
* unless a database uses very different SQL this should suffice.
*
* @param string $sql query to scan and adjust
*
* @return string
*/
public function glueLimitOne( $sql );
/**
* Returns the tables that are in the database.
*
* @return array
*/
public function getTables();
/**
* This method will create a table for the bean.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type of bean you want to create a table for
*
* @return void
*/
public function createTable( $type );
/**
* Returns an array containing all the columns of the specified type.
* The format of the return array looks like this:
* $field => $type where $field is the name of the column and $type
* is a database specific description of the datatype.
*
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type of bean you want to obtain a column list of
*
* @return array
*/
public function getColumns( $type );
/**
* Returns the Column Type Code (integer) that corresponds
* to the given value type. This method is used to determine the minimum
* column type required to represent the given value. There are two modes of
* operation: with or without special types. Scanning without special types
* requires the second parameter to be set to FALSE. This is useful when the
* column has already been created and prevents it from being modified to
* an incompatible type leading to data loss. Special types will be taken
* into account when a column does not exist yet (parameter is then set to TRUE).
*
* Special column types are determines by the AQueryWriter constant
* C_DATA_TYPE_ONLY_IF_NOT_EXISTS (usually 80). Another 'very special' type is type
* C_DATA_TYPE_MANUAL (usually 99) which represents a user specified type. Although
* no special treatment has been associated with the latter for now.
*
* @param string $value value
* @param boolean $alsoScanSpecialForTypes take special types into account
*
* @return integer
*/
public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
/**
* This method will add a column to a table.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type name of the table
* @param string $column name of the column
* @param integer $field data type for field
*
* @return void
*/
public function addColumn( $type, $column, $field );
/**
* Returns the Type Code for a Column Description.
* Given an SQL column description this method will return the corresponding
* code for the writer. If the include specials flag is set it will also
* return codes for special columns. Otherwise special columns will be identified
* as specified columns.
*
* @param string $typedescription description
* @param boolean $includeSpecials whether you want to get codes for special columns as well
*
* @return integer
*/
public function code( $typedescription, $includeSpecials = FALSE );
/**
* This method will widen the column to the specified data type.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type / table that needs to be adjusted
* @param string $column column that needs to be altered
* @param integer $datatype target data type
*
* @return void
*/
public function widenColumn( $type, $column, $datatype );
/**
* Selects records from the database.
* This methods selects the records from the database that match the specified
* type, conditions (optional) and additional SQL snippet (optional).
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string $addSql additional SQL snippet
* @param array $bindings bindings for SQL snippet
*
* @return array
*/
public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
/**
* Selects records from the database and returns a cursor.
* This methods selects the records from the database that match the specified
* type, conditions (optional) and additional SQL snippet (optional).
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string $addSQL additional SQL snippet
* @param array $bindings bindings for SQL snippet
*
* @return Cursor
*/
public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() );
/**
* Returns records through an intermediate type. This method is used to obtain records using a link table and
* allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
*
* @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
* @param string $destType destination type, the target type you want to get beans of
* @param mixed $linkID ID to use for the link table
* @param string $addSql Additional SQL snippet
* @param array $bindings Bindings for SQL snippet
*
* @return array
*/
public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
/**
* Returns the row that links $sourceType $sourcID to $destType $destID in an N-M relation.
*
* @param string $sourceType source type, the first part of the link you're looking for
* @param string $destType destination type, the second part of the link you're looking for
* @param string $sourceID ID for the source
* @param string $destID ID for the destination
*
* @return array|null
*/
public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
/**
* Counts the number of records in the database that match the
* conditions and additional SQL.
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string $addSQL additional SQL snippet
* @param array $bindings bindings for SQL snippet
*
* @return integer
*/
public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
/**
* Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
*
* @param string $sourceType source type
* @param string $targetType the thing you want to count
* @param mixed $linkID the of the source type
* @param string $addSQL additional SQL snippet
* @param array $bindings bindings for SQL snippet
*
* @return integer
*/
public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
/**
* Returns all rows of specified type that have been tagged with one of the
* strings in the specified tag list array.
*
* Note that the additional SQL snippet can only be used for pagination,
* the SQL snippet will be appended to the end of the query.
*
* @param string $type the bean type you want to query
* @param array $tagList an array of strings, each string containing a tag title
* @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
* @param string $addSql addition SQL snippet, for pagination
* @param array $bindings parameter bindings for additional SQL snippet
*
* @return array
*/
public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
/**
* Like queryTagged but only counts.
*
* @param string $type the bean type you want to query
* @param array $tagList an array of strings, each string containing a tag title
* @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
* @param string $addSql addition SQL snippet, for pagination
* @param array $bindings parameter bindings for additional SQL snippet
*
* @return integer
*/
public function queryCountTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
/**
* Returns all parent rows or child rows of a specified row.
* Given a type specifier and a primary key id, this method returns either all child rows
* as defined by having <type>_id = id or all parent rows as defined per id = <type>_id
* taking into account an optional SQL snippet along with parameters.
*
* The $select parameter can be used to adjust the select snippet of the query.
* Possible values are: C_CTE_SELECT_NORMAL (just select all columns, default), C_CTE_SELECT_COUNT
* (count rows) used for countParents and countChildren functions - or you can specify a
* string yourself like 'count(distinct brand)'.
*
* @param string $type the bean type you want to query rows for
* @param integer $id id of the reference row
* @param boolean $up TRUE to query parent rows, FALSE to query child rows
* @param string $addSql optional SQL snippet to embed in the query
* @param array $bindings parameter bindings for additional SQL snippet
* @param mixed $select Select Snippet to use when querying (optional)
*
* @return array
*/
public function queryRecursiveCommonTableExpression( $type, $id, $up = TRUE, $addSql = NULL, $bindings = array(), $select = QueryWriter::C_CTE_SELECT_NORMAL );
/**
* This method should update (or insert a record), it takes
* a table name, a list of update values ( $field => $value ) and an
* primary key ID (optional). If no primary key ID is provided, an
* INSERT will take place.
* Returns the new ID.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type name of the table to update
* @param array $updatevalues list of update values
* @param integer $id optional primary key ID value
*
* @return integer
*/
public function updateRecord( $type, $updatevalues, $id = NULL );
/**
* Deletes records from the database.
* @note $addSql is always prefixed with ' WHERE ' or ' AND .'
*
* @param string $type name of the table you want to query
* @param array $conditions criteria ( $column => array( $values ) )
* @param string $addSql additional SQL
* @param array $bindings bindings
*
* @return void
*/
public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
/**
* Deletes all links between $sourceType and $destType in an N-M relation.
*
* @param string $sourceType source type
* @param string $destType destination type
* @param string $sourceID source ID
*
* @return void
*/
public function deleteRelations( $sourceType, $destType, $sourceID );
/**
* @see QueryWriter::addUniqueConstaint
*/
public function addUniqueIndex( $type, $columns );
/**
* This method will add a UNIQUE constraint index to a table on columns $columns.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type target bean type
* @param array $columnsPartOfIndex columns to include in index
*
* @return void
*/
public function addUniqueConstraint( $type, $columns );
/**
* This method will check whether the SQL state is in the list of specified states
* and returns TRUE if it does appear in this list or FALSE if it
* does not. The purpose of this method is to translate the database specific state to
* a one of the constants defined in this class and then check whether it is in the list
* of standard states provided.
*
* @param string $state SQL state to consider
* @param array $list list of standardized SQL state constants to check against
* @param array $extraDriverDetails Some databases communicate state information in a driver-specific format
* rather than through the main sqlState code. For those databases, this extra
* information can be used to determine the standardized state
*
* @return boolean
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() );
/**
* This method will remove all beans of a certain type.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type bean type
*
* @return void
*/
public function wipe( $type );
/**
* This method will add a foreign key from type and field to
* target type and target field.
* The foreign key is created without an action. On delete/update
* no action will be triggered. The FK is only used to allow database
* tools to generate pretty diagrams and to make it easy to add actions
* later on.
* This methods accepts a type and infers the corresponding table name.
*
*
* @param string $type type that will have a foreign key field
* @param string $targetType points to this type
* @param string $property field that contains the foreign key value
* @param string $targetProperty field where the fk points to
* @param string $isDep whether target is dependent and should cascade on update/delete
*
* @return void
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE );
/**
* This method will add an index to a type and field with name
* $name.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type to add index to
* @param string $name name of the new index
* @param string $property field to index
*
* @return void
*/
public function addIndex( $type, $name, $property );
/**
* Checks and filters a database structure element like a table of column
* for safe use in a query. A database structure has to conform to the
* RedBeanPHP DB security policy which basically means only alphanumeric
* symbols are allowed. This security policy is more strict than conventional
* SQL policies and does therefore not require database specific escaping rules.
*
* @param string $databaseStructure name of the column/table to check
* @param boolean $noQuotes TRUE to NOT put backticks or quotes around the string
*
* @return string
*/
public function esc( $databaseStructure, $dontQuote = FALSE );
/**
* Removes all tables and views from the database.
*
* @return void
*/
public function wipeAll();
/**
* Renames an association. For instance if you would like to refer to
* album_song as: track you can specify this by calling this method like:
*
* <code>
* renameAssociation('album_song','track')
* </code>
*
* This allows:
*
* <code>
* $album->sharedSong
* </code>
*
* to add/retrieve beans from track instead of album_song.
* Also works for exportAll().
*
* This method also accepts a single associative array as
* its first argument.
*
* @param string|array $fromType original type name, or array
* @param string $toType new type name (only if 1st argument is string)
*
* @return void
*/
public function renameAssocTable( $fromType, $toType = NULL );
/**
* Returns the format for link tables.
* Given an array containing two type names this method returns the
* name of the link table to be used to store and retrieve
* association records. For instance, given two types: person and
* project, the corresponding link table might be: 'person_project'.
*
* @param array $types two types array($type1, $type2)
*
* @return string
*/
public function getAssocTable( $types );
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,364 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP CUBRID Writer.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the CUBRID database platform.
*
* @file RedBeanPHP/QueryWriter/CUBRID.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class CUBRID extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_DOUBLE = 1;
const C_DATATYPE_STRING = 2;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* This method adds a foreign key from type and field to
* target type and target field.
* The foreign key is created without an action. On delete/update
* no action will be triggered. The FK is only used to allow database
* tools to generate pretty diagrams and to make it easy to add actions
* later on.
* This methods accepts a type and infers the corresponding table name.
*
* @param string $type type that will have a foreign key field
* @param string $targetType points to this type
* @param string $property field that contains the foreign key value
* @param string $targetProperty field where the fk points to
* @param boolean $isDep is dependent
*
* @return bool
*/
protected function buildFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
$table = $this->esc( $type );
$tableNoQ = $this->esc( $type, TRUE );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$column = $this->esc( $property );
$columnNoQ = $this->esc( $property, TRUE );
$targetColumn = $this->esc( $targetProperty );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $columnNoQ ) ) ) return FALSE;
$needsToDropFK = FALSE;
$casc = ( $isDep ? 'CASCADE' : 'SET NULL' );
$sql = "ALTER TABLE $table ADD CONSTRAINT FOREIGN KEY($column) REFERENCES $targetTable($targetColumn) ON DELETE $casc ";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$sqlCode = $this->adapter->get("SHOW CREATE TABLE `{$type}`");
if (!isset($sqlCode[0])) return array();
$matches = array();
preg_match_all( '/CONSTRAINT\s+\[([\w_]+)\]\s+FOREIGN\s+KEY\s+\(\[([\w_]+)\]\)\s+REFERENCES\s+\[([\w_]+)\](\s+ON\s+DELETE\s+(CASCADE|SET\sNULL|RESTRICT|NO\sACTION)\s+ON\s+UPDATE\s+(SET\sNULL|RESTRICT|NO\sACTION))?/', $sqlCode[0]['CREATE TABLE'], $matches );
$list = array();
if (!isset($matches[0])) return $list;
$max = count($matches[0]);
for($i = 0; $i < $max; $i++) {
$label = $this->makeFKLabel( $matches[2][$i], $matches[3][$i], 'id' );
$list[ $label ] = array(
'name' => $matches[1][$i],
'from' => $matches[2][$i],
'table' => $matches[3][$i],
'to' => 'id',
'on_update' => $matches[6][$i],
'on_delete' => $matches[5][$i]
);
}
return $list;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
CUBRID::C_DATATYPE_INTEGER => ' INTEGER ',
CUBRID::C_DATATYPE_DOUBLE => ' DOUBLE ',
CUBRID::C_DATATYPE_STRING => ' STRING ',
CUBRID::C_DATATYPE_SPECIAL_DATE => ' DATE ',
CUBRID::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( ( $v ) )] = $k;
}
$this->sqltype_typeno['STRING(1073741823)'] = self::C_DATATYPE_STRING;
$this->adapter = $adapter;
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
$rows = $this->adapter->getCol( "SELECT class_name FROM db_class WHERE is_system_class = 'NO';" );
return $rows;
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $table )
{
$sql = 'CREATE TABLE '
. $this->esc( $table )
. ' ("id" integer AUTO_INCREMENT, CONSTRAINT "pk_'
. $this->esc( $table, TRUE )
. '_id" PRIMARY KEY("id"))';
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table );
$columnsRaw = $this->adapter->get( "SHOW COLUMNS FROM $table" );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['Field']] = $r['Type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( is_null( $value ) ) {
return self::C_DATATYPE_INTEGER;
}
if ( $flagSpecial ) {
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return self::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
return self::C_DATATYPE_SPECIAL_DATETIME;
}
}
$value = strval( $value );
if ( !$this->startsWithZeros( $value ) ) {
if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= -2147483647 && $value <= 2147483647 ) {
return self::C_DATATYPE_INTEGER;
}
if ( is_numeric( $value ) ) {
return self::C_DATATYPE_DOUBLE;
}
}
return self::C_DATATYPE_STRING;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED );
if ( $includeSpecials ) {
return $r;
}
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::addColumn
*/
public function addColumn( $type, $column, $field )
{
$table = $type;
$type = $field;
$table = $this->esc( $table );
$column = $this->esc( $column );
$type = array_key_exists( $type, $this->typeno_sqltype ) ? $this->typeno_sqltype[$type] : '';
$this->adapter->exec( "ALTER TABLE $table ADD COLUMN $column $type " );
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // else we get multiple indexes due to order-effects
$name = 'UQ_' . sha1( implode( ',', $columns ) );
$sql = "ALTER TABLE $table ADD CONSTRAINT UNIQUE $name (" . implode( ',', $columns ) . ")";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
return ( $state == 'HY000' ) ? ( count( array_diff( array(
QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
), $list ) ) !== 3 ) : FALSE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $column )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $column );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDependent );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
foreach ( $this->getTables() as $t ) {
foreach ( $this->getKeyMapForType( $t ) as $k ) {
$this->adapter->exec( "ALTER TABLE \"$t\" DROP FOREIGN KEY \"{$k['name']}\"" );
}
}
foreach ( $this->getTables() as $t ) {
$this->adapter->exec( "DROP TABLE \"$t\"" );
}
}
/**
* @see QueryWriter::esc
*/
public function esc( $dbStructure, $noQuotes = FALSE )
{
return parent::esc( strtolower( $dbStructure ), $noQuotes );
}
}

View File

@ -0,0 +1,352 @@
<?php
namespace RedBeanPHP\QueryWriter;
/* Experimental */
/**
* This driver has been created in 2015 but it has never been distributed
* because it was never finished. However, in the true spirit of open source
* it is now available in the source of RedBeanPHP.
*
* Consider this driver experimental or help me finish it to
* support the Firebird/Interbase database series.
*
*/
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP Firebird/Interbase QueryWriter.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the Firebird/Interbase database platform.
*
* *** Warning - Experimental Software | Not ready for Production ****
*
* @file RedBeanPHP/QueryWriter/Firebird.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Firebird extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 2;
const C_DATATYPE_FLOAT = 3;
const C_DATATYPE_TEXT = 5;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '"';
/**
* Returns the insert suffix SQL Snippet
*
* @param string $table table
*
* @return string $sql SQL Snippet
*/
protected function getInsertSuffix( $table )
{
return 'RETURNING id ';
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get('
SELECT
information_schema.key_column_usage.constraint_name AS `name`,
information_schema.key_column_usage.referenced_table_name AS `table`,
information_schema.key_column_usage.column_name AS `from`,
information_schema.key_column_usage.referenced_column_name AS `to`,
information_schema.referential_constraints.update_rule AS `on_update`,
information_schema.referential_constraints.delete_rule AS `on_delete`
FROM information_schema.key_column_usage
INNER JOIN information_schema.referential_constraints
ON (
information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
AND information_schema.referential_constraints.constraint_schema = information_schema.key_column_usage.constraint_schema
AND information_schema.referential_constraints.constraint_catalog = information_schema.key_column_usage.constraint_catalog
)
WHERE
information_schema.key_column_usage.table_schema IN ( SELECT DATABASE() )
AND information_schema.key_column_usage.table_name = ?
AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
', array($table));
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
Firebird::C_DATATYPE_INTEGER => 'INTEGER',
Firebird::C_DATATYPE_FLOAT => 'FLOAT',
Firebird::C_DATATYPE_TEXT => 'VARCHAR(8190)',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtoupper( $v ) )] = $k;
}
$this->adapter = $adapter;
$this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer $const data type to be used for IDS.
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
WHERE RDB$VIEW_BLR IS NULL AND
(RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0)');
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $table )
{
$tableNoQ = $this->esc( $table );
$tableSQL = "CREATE TABLE \"{$table}\" ( id INT )";
$dropGeneratorSQL = "DROP GENERATOR gen{$table}";
$generatorSQL = "CREATE GENERATOR gen{$table}";
$generatorSQL2 = "SET GENERATOR gen{$table} TO 0";
$triggerSQL = "
CREATE TRIGGER ai{$table} FOR \"{$table}\"
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.id is NULL) then NEW.id = GEN_ID(gen{$table}, 1);
END
";
try { $this->adapter->exec( $dropGeneratorSQL ); }catch( SQLException $e ) {};
$this->adapter->exec( $tableSQL );
$this->adapter->exec( $generatorSQL );
$this->adapter->exec( $generatorSQL2 );
$this->adapter->exec( $triggerSQL );
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $type, $property, $dataType )
{
if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
$table = $this->esc( $type );
$column = $this->esc( $property );
$newType = $this->typeno_sqltype[$dataType];
$this->adapter->exec( "ALTER TABLE $table ALTER COLUMN $column TYPE $newType " );
return TRUE;
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$columnsRaw = $this->adapter->getAssoc( '
SELECT
RDB$RELATION_FIELDS.RDB$FIELD_NAME,
CASE RDB$FIELDS.RDB$FIELD_TYPE
WHEN 10 THEN \'FLOAT\'
WHEN 8 THEN \'INTEGER\'
WHEN 37 THEN \'VARCHAR\'
ELSE RDB$FIELDS.RDB$FIELD_TYPE
END AS FTYPE
FROM RDB$RELATION_FIELDS
LEFT JOIN RDB$FIELDS ON RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME
WHERE RDB$RELATION_FIELDS.RDB$RELATION_NAME = \''.($this->esc($table, true)).'\'
ORDER BY RDB$RELATION_FIELDS.RDB$FIELD_POSITION
');
$columns = array();
foreach( $columnsRaw as $rawKey => $columnRaw ) {
$columns[ trim( $rawKey ) ] = trim( $columnRaw );
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
if ( AQueryWriter::canBeTreatedAsInt( $value ) ) {
return FireBird::C_DATATYPE_INTEGER;
}
if ( !$this->startsWithZeros( $value ) && is_numeric( $value ) ) {
return FireBird::C_DATATYPE_DOUBLE;
}
return FireBird::C_DATATYPE_TEXT;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
return $this->sqltype_typeno[$typedescription];
} else {
return self::C_DATATYPE_SPECIFIED;
}
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // Else we get multiple indexes due to order-effects
$name = 'UQ_'.substr( sha1( implode( ',', $columns ) ), 0, 28);
try {
$sql = "ALTER TABLE $table
ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
$this->adapter->exec( $sql );
} catch ( SQLException $e ) {
//do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$field = $this->esc( $property );
$fieldNoQ = $this->esc( $property, TRUE );
$targetField = $this->esc( $targetProperty );
$targetFieldNoQ = $this->esc( $targetProperty, TRUE );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
//Widen the column if it's incapable of representing a foreign key (at least INT).
$columns = $this->getColumns( $tableNoQ );
$idType = $this->getTypeForID();
if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
$this->widenColumn( $type, $property, $idType );
}
$fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
$cName = 'c_'.$fkName;
try {
$this->adapter->exec( "
ALTER TABLE {$table}
ADD CONSTRAINT $cName
FOREIGN KEY $fkName ( {$fieldNoQ} ) REFERENCES {$targetTableNoQ}
({$targetFieldNoQ}) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
} catch ( SQLException $e ) {
// Failure of fk-constraints is not a problem
}
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
);
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$tables = $this->getTables();
foreach( $tables as $table ) {
$table = trim( $table );
$this->adapter->exec( "DROP TABLE \"{$table}\" " );
}
}
}

View File

@ -0,0 +1,460 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP MySQLWriter.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the MySQL/MariaDB database platform.
*
* @file RedBeanPHP/QueryWriter/MySQL.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class MySQL extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_BOOL = 0;
const C_DATATYPE_UINT32 = 2;
const C_DATATYPE_DOUBLE = 3;
const C_DATATYPE_TEXT7 = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
const C_DATATYPE_TEXT8 = 5;
const C_DATATYPE_TEXT16 = 6;
const C_DATATYPE_TEXT32 = 7;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIAL_TIME = 83; //MySQL time column (only manual)
const C_DATATYPE_SPECIAL_POINT = 90;
const C_DATATYPE_SPECIAL_LINESTRING = 91;
const C_DATATYPE_SPECIAL_POLYGON = 92;
const C_DATATYPE_SPECIAL_MONEY = 93;
const C_DATATYPE_SPECIAL_JSON = 94; //JSON support (only manual)
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE %s ADD %s %s '
),
'createTable' => array(
'*' => 'CREATE TABLE %s (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET=%s COLLATE=%s '
),
'widenColumn' => array(
'*' => 'ALTER TABLE `%s` CHANGE %s %s %s '
)
);
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$databaseName = $this->adapter->getCell('SELECT DATABASE()');
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get('
SELECT
information_schema.key_column_usage.constraint_name AS `name`,
information_schema.key_column_usage.referenced_table_name AS `table`,
information_schema.key_column_usage.column_name AS `from`,
information_schema.key_column_usage.referenced_column_name AS `to`,
information_schema.referential_constraints.update_rule AS `on_update`,
information_schema.referential_constraints.delete_rule AS `on_delete`
FROM information_schema.key_column_usage
INNER JOIN information_schema.referential_constraints
ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
WHERE
information_schema.key_column_usage.table_schema = :database
AND information_schema.referential_constraints.constraint_schema = :database
AND information_schema.key_column_usage.constraint_schema = :database
AND information_schema.key_column_usage.table_name = :table
AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
', array( ':database' => $databaseName, ':table' => $table ) );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
* @param array $options options array
*/
public function __construct( Adapter $adapter, $options = array() )
{
$this->typeno_sqltype = array(
MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ',
MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
MySQL::C_DATATYPE_SPECIAL_TIME => ' TIME ',
MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) ',
MYSQL::C_DATATYPE_SPECIAL_JSON => ' JSON '
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
$this->adapter = $adapter;
$this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
$me = $this;
if (!isset($options['noInitcode']))
$this->adapter->setInitCode(function($version) use(&$me) {
try {
if (strpos($version, 'maria')===FALSE && intval($version)>=8) {
$me->useFeature('ignoreDisplayWidth');
}
} catch( \Exception $e ){}
});
}
/**
* Enables certain features/dialects.
*
* - ignoreDisplayWidth required for MySQL8+
* (automatically set by setup() if you pass dsn instead of PDO object)
*
* @param string $name feature ID
*
* @return void
*/
public function useFeature($name) {
if ($name == 'ignoreDisplayWidth') {
$this->typeno_sqltype[MySQL::C_DATATYPE_BOOL] = ' TINYINT UNSIGNED ';
$this->typeno_sqltype[MySQL::C_DATATYPE_UINT32] = ' INT UNSIGNED ';
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
}
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_UINT32;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'show tables' );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
$charset = $charset_collate['charset'];
$collate = $charset_collate['collate'];
$sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table, $charset, $collate );
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['Field']] = $r['Type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
if ( $flagSpecial ) {
if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_MONEY;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_DATETIME;
}
if ( preg_match( '/^POINT\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_POINT;
}
if ( preg_match( '/^LINESTRING\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
}
if ( preg_match( '/^POLYGON\(/', $value ) ) {
return MySQL::C_DATATYPE_SPECIAL_POLYGON;
}
if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
return self::C_DATATYPE_SPECIAL_JSON;
}
}
//setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' || $value === 0 || $value === 1 ) {
return MySQL::C_DATATYPE_BOOL;
}
if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
if ( !$this->startsWithZeros( $value ) ) {
if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
return MySQL::C_DATATYPE_UINT32;
}
if ( is_numeric( $value ) ) {
return MySQL::C_DATATYPE_DOUBLE;
}
}
if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
return MySQL::C_DATATYPE_TEXT7;
}
if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
return MySQL::C_DATATYPE_TEXT8;
}
if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
return MySQL::C_DATATYPE_TEXT16;
}
return MySQL::C_DATATYPE_TEXT32;
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
$r = $this->sqltype_typeno[$typedescription];
} else {
$r = self::C_DATATYPE_SPECIFIED;
}
if ( $includeSpecials ) {
return $r;
}
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); // Else we get multiple indexes due to order-effects
$name = 'UQ_' . sha1( implode( ',', $columns ) );
try {
$sql = "ALTER TABLE $table
ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
$this->adapter->exec( $sql );
} catch ( SQLException $e ) {
//do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
try {
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
* @return bool
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$targetTableNoQ = $this->esc( $targetType, TRUE );
$field = $this->esc( $property );
$fieldNoQ = $this->esc( $property, TRUE );
$targetField = $this->esc( $targetProperty );
$targetFieldNoQ = $this->esc( $targetProperty, TRUE );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
//Widen the column if it's incapable of representing a foreign key (at least INT).
$columns = $this->getColumns( $tableNoQ );
$idType = $this->getTypeForID();
if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
$this->widenColumn( $type, $property, $idType );
}
$fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
$cName = 'c_'.$fkName;
try {
$this->adapter->exec( "
ALTER TABLE {$table}
ADD CONSTRAINT $cName
FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
(`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
} catch ( SQLException $e ) {
// Failure of fk-constraints is not a problem
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
);
if ( $state == 'HY000' && !empty( $extraDriverDetails[1] ) ) {
$driverCode = $extraDriverDetails[1];
if ( $driverCode == '1205' && in_array( QueryWriter::C_SQLSTATE_LOCK_TIMEOUT, $list ) ) {
return TRUE;
}
}
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
foreach ( $this->getTables() as $t ) {
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
}
$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
}
}

View File

@ -0,0 +1,436 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP PostgreSQL Query Writer.
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the PostgreSQL database platform.
*
* @file RedBeanPHP/QueryWriter/PostgreSQL.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class PostgreSQL extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_DOUBLE = 1;
const C_DATATYPE_TEXT = 3;
const C_DATATYPE_SPECIAL_DATE = 80;
const C_DATATYPE_SPECIAL_DATETIME = 81;
const C_DATATYPE_SPECIAL_TIME = 82; //TIME (no zone) only manual
const C_DATATYPE_SPECIAL_TIMEZ = 83; //TIME (plus zone) only manual
const C_DATATYPE_SPECIAL_POINT = 90;
const C_DATATYPE_SPECIAL_LSEG = 91;
const C_DATATYPE_SPECIAL_CIRCLE = 92;
const C_DATATYPE_SPECIAL_MONEY = 93;
const C_DATATYPE_SPECIAL_POLYGON = 94;
const C_DATATYPE_SPECIAL_MONEY2 = 95; //Numbers only money, i.e. fixed point numeric
const C_DATATYPE_SPECIAL_JSON = 96; //JSON support (only manual)
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '"';
/**
* @var string
*/
protected $defaultValue = 'DEFAULT';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE %s ADD %s %s '
),
'createTable' => array(
'*' => 'CREATE TABLE %s (id SERIAL PRIMARY KEY) '
),
'widenColumn' => array(
'*' => 'ALTER TABLE %s ALTER COLUMN %s TYPE %s'
)
);
/**
* Returns the insert suffix SQL Snippet
*
* @param string $table table
*
* @return string $sql SQL Snippet
*/
protected function getInsertSuffix( $table )
{
return 'RETURNING id ';
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get( '
SELECT
information_schema.key_column_usage.constraint_name AS "name",
information_schema.key_column_usage.column_name AS "from",
information_schema.constraint_table_usage.table_name AS "table",
information_schema.constraint_column_usage.column_name AS "to",
information_schema.referential_constraints.update_rule AS "on_update",
information_schema.referential_constraints.delete_rule AS "on_delete"
FROM information_schema.key_column_usage
INNER JOIN information_schema.constraint_table_usage
ON (
information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
)
INNER JOIN information_schema.constraint_column_usage
ON (
information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
)
INNER JOIN information_schema.referential_constraints
ON (
information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
)
WHERE
information_schema.key_column_usage.table_catalog = current_database()
AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
AND information_schema.key_column_usage.table_name = ?
', array( $type ) );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $k['name'],
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
self::C_DATATYPE_INTEGER => ' integer ',
self::C_DATATYPE_DOUBLE => ' double precision ',
self::C_DATATYPE_TEXT => ' text ',
self::C_DATATYPE_SPECIAL_DATE => ' date ',
self::C_DATATYPE_SPECIAL_TIME => ' time ',
self::C_DATATYPE_SPECIAL_TIMEZ => ' time with time zone ',
self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
self::C_DATATYPE_SPECIAL_POINT => ' point ',
self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
self::C_DATATYPE_SPECIAL_MONEY => ' money ',
self::C_DATATYPE_SPECIAL_MONEY2 => ' numeric(10,2) ',
self::C_DATATYPE_SPECIAL_POLYGON => ' polygon ',
self::C_DATATYPE_SPECIAL_JSON => ' json ',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
}
$this->adapter = $adapter;
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::getTables
*/
public function getTables()
{
return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'createTable', $type ), $table ) );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table, TRUE );
$columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
$columns = array();
foreach ( $columnsRaw as $r ) {
$columns[$r['column_name']] = $r['data_type'];
}
return $columns;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( $value === INF ) return self::C_DATATYPE_TEXT;
if ( $flagSpecial && $value ) {
if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
}
if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
}
if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
}
if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
}
if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
}
if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
}
if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
}
if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
}
if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
return self::C_DATATYPE_SPECIAL_JSON;
}
}
if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
&& AQueryWriter::canBeTreatedAsInt( $value )
&& $value < 2147483648
&& $value > -2147483648 )
) {
return self::C_DATATYPE_INTEGER;
} elseif ( is_numeric( $value ) ) {
return self::C_DATATYPE_DOUBLE;
} else {
return self::C_DATATYPE_TEXT;
}
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
if ( $includeSpecials ) return $r;
if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
return self::C_DATATYPE_SPECIFIED;
}
return $r;
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $beanType, $column, $datatype )
{
$table = $beanType;
$type = $datatype;
$table = $this->esc( $table );
$column = $this->esc( $column );
$newtype = $this->typeno_sqltype[$type];
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'widenColumn', $beanType, $column ), $table, $column, $newtype ) );
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$columns = array();
foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
$table = $this->esc( $type );
sort( $columns ); //else we get multiple indexes due to order-effects
$name = "UQ_" . sha1( $table . implode( ',', $columns ) );
$sql = "ALTER TABLE {$table}
ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
try {
$this->adapter->exec( $sql );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
'42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
'23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
'55P03' => QueryWriter::C_SQLSTATE_LOCK_TIMEOUT
);
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $property )
{
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $property );
try {
$this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
$table = $this->esc( $type );
$targetTable = $this->esc( $targetType );
$field = $this->esc( $property );
$targetField = $this->esc( $targetProperty );
$tableNoQ = $this->esc( $type, TRUE );
$fieldNoQ = $this->esc( $property, TRUE );
if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
try{
$delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
$this->adapter->exec( "ALTER TABLE {$table}
ADD FOREIGN KEY ( {$field} ) REFERENCES {$targetTable}
({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
return TRUE;
} catch ( SQLException $e ) {
return FALSE;
}
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
foreach ( $this->getTables() as $t ) {
$t = $this->esc( $t );
//Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
}
$this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}

View File

@ -0,0 +1,496 @@
<?php
namespace RedBeanPHP\QueryWriter;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
use RedBeanPHP\RedException\SQL as SQLException;
/**
* RedBeanPHP SQLiteWriter with support for SQLite types
* This is a QueryWriter class for RedBeanPHP.
* This QueryWriter provides support for the SQLite database platform.
*
* @file RedBeanPHP/QueryWriter/SQLiteT.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SQLiteT extends AQueryWriter implements QueryWriter
{
/**
* Data types
*/
const C_DATATYPE_INTEGER = 0;
const C_DATATYPE_NUMERIC = 1;
const C_DATATYPE_TEXT = 2;
const C_DATATYPE_SPECIFIED = 99;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* @var string
*/
protected $quoteCharacter = '`';
/**
* @var array
*/
protected $DDLTemplates = array(
'addColumn' => array(
'*' => 'ALTER TABLE `%s` ADD `%s` %s'
),
'createTable' => array(
'*' => 'CREATE TABLE %s ( id INTEGER PRIMARY KEY AUTOINCREMENT )'
),
'widenColumn' => array(
'*' => ',`%s` %s '
)
);
/**
* Gets all information about a table (from a type).
*
* Format:
* array(
* name => name of the table
* columns => array( name => datatype )
* indexes => array() raw index information rows from PRAGMA query
* keys => array() raw key information rows from PRAGMA query
* )
*
* @param string $type type you want to get info of
*
* @return array
*/
protected function getTable( $type )
{
$tableName = $this->esc( $type, TRUE );
$columns = $this->getColumns( $type );
$indexes = $this->getIndexes( $type );
$keys = $this->getKeyMapForType( $type );
$table = array(
'columns' => $columns,
'indexes' => $indexes,
'keys' => $keys,
'name' => $tableName
);
$this->tableArchive[$tableName] = $table;
return $table;
}
/**
* Puts a table. Updates the table structure.
* In SQLite we can't change columns, drop columns, change or add foreign keys so we
* have a table-rebuild function. You simply load your table with getTable(), modify it and
* then store it with putTable()...
*
* @param array $tableMap information array
*
* @return void
*/
protected function putTable( $tableMap )
{
$table = $tableMap['name'];
$q = array();
$q[] = "DROP TABLE IF EXISTS tmp_backup;";
$oldColumnNames = array_keys( $this->getColumns( $table ) );
foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
$q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
$q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
$q[] = "PRAGMA foreign_keys = 0 ";
$q[] = "DROP TABLE `$table`;";
$newTableDefStr = '';
foreach ( $tableMap['columns'] as $column => $type ) {
if ( $column != 'id' ) {
$newTableDefStr .= sprintf( $this->getDDLTemplate( 'widenColumn', $table, $column ), $column, $type );
}
}
$fkDef = '';
foreach ( $tableMap['keys'] as $key ) {
$fkDef .= ", FOREIGN KEY(`{$key['from']}`)
REFERENCES `{$key['table']}`(`{$key['to']}`)
ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
}
$q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
foreach ( $tableMap['indexes'] as $name => $index ) {
if ( strpos( $name, 'UQ_' ) === 0 ) {
$cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
$q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
} else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
}
$q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
$q[] = "DROP TABLE tmp_backup;";
$q[] = "PRAGMA foreign_keys = 1 ";
foreach ( $q as $sq ) $this->adapter->exec( $sq );
}
/**
* Returns the an array describing the indexes for type $type.
*
* @param string $type type to describe indexes of
*
* @return array
*/
protected function getIndexes( $type )
{
$table = $this->esc( $type, TRUE );
$indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
$indexInfoList = array();
foreach ( $indexes as $i ) {
$indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
$indexInfoList[$i['name']]['unique'] = $i['unique'];
}
return $indexInfoList;
}
/**
* Adds a foreign key to a type.
* Note: cant put this in try-catch because that can hide the fact
* that database has been damaged.
*
* @param string $type type you want to modify table of
* @param string $targetType target type
* @param string $field field of the type that needs to get the fk
* @param string $targetField field where the fk needs to point to
* @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
*
* @return boolean
*/
protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
{
$table = $this->esc( $type, TRUE );
$targetTable = $this->esc( $targetType, TRUE );
$column = $this->esc( $property, TRUE );
$targetColumn = $this->esc( $targetProperty, TRUE );
$tables = $this->getTables();
if ( !in_array( $targetTable, $tables ) ) return FALSE;
if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
$t = $this->getTable( $table );
$consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
$label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
$t['keys'][$label] = array(
'table' => $targetTable,
'from' => $column,
'to' => $targetColumn,
'on_update' => $consSQL,
'on_delete' => $consSQL
);
$this->putTable( $t );
return TRUE;
}
/**
* @see AQueryWriter::getKeyMapForType
*/
protected function getKeyMapForType( $type )
{
$table = $this->esc( $type, TRUE );
$keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
$keyInfoList = array();
foreach ( $keys as $k ) {
$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
$keyInfoList[$label] = array(
'name' => $label,
'from' => $k['from'],
'table' => $k['table'],
'to' => $k['to'],
'on_update' => $k['on_update'],
'on_delete' => $k['on_delete']
);
}
return $keyInfoList;
}
/**
* Constructor
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param Adapter $adapter Database Adapter
*/
public function __construct( Adapter $adapter )
{
$this->typeno_sqltype = array(
SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
SQLiteT::C_DATATYPE_TEXT => 'TEXT',
);
$this->sqltype_typeno = array();
foreach ( $this->typeno_sqltype as $k => $v ) {
$this->sqltype_typeno[$v] = $k;
}
$this->adapter = $adapter;
$this->adapter->setOption( 'setInitQuery', ' PRAGMA foreign_keys = 1 ' );
}
/**
* This method returns the datatype to be used for primary key IDS and
* foreign keys. Returns one if the data type constants.
*
* @return integer $const data type to be used for IDS.
*/
public function getTypeForID()
{
return self::C_DATATYPE_INTEGER;
}
/**
* @see QueryWriter::scanType
*/
public function scanType( $value, $flagSpecial = FALSE )
{
$this->svalue = $value;
if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
if ( $value === INF ) return self::C_DATATYPE_TEXT;
if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
|| preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
|| preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
) {
return self::C_DATATYPE_NUMERIC;
}
return self::C_DATATYPE_TEXT;
}
/**
* @see QueryWriter::addColumn
*/
public function addColumn( $table, $column, $type )
{
$column = $this->check( $column );
$table = $this->check( $table );
$type = $this->typeno_sqltype[$type];
$this->adapter->exec( sprintf( $this->getDDLTemplate( 'addColumn', $table, $column ), $table, $column, $type ) );
}
/**
* @see QueryWriter::code
*/
public function code( $typedescription, $includeSpecials = FALSE )
{
$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
return $r;
}
/**
* @see QueryWriter::widenColumn
*/
public function widenColumn( $type, $column, $datatype )
{
$t = $this->getTable( $type );
$t['columns'][$column] = $this->typeno_sqltype[$datatype];
$this->putTable( $t );
}
/**
* @see QueryWriter::getTables();
*/
public function getTables()
{
return $this->adapter->getCol( "SELECT name FROM sqlite_master
WHERE type='table' AND name!='sqlite_sequence';" );
}
/**
* @see QueryWriter::createTable
*/
public function createTable( $type )
{
$table = $this->esc( $type );
$sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table );
$this->adapter->exec( $sql );
}
/**
* @see QueryWriter::getColumns
*/
public function getColumns( $table )
{
$table = $this->esc( $table, TRUE );
$columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
$columns = array();
foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
return $columns;
}
/**
* @see QueryWriter::addUniqueIndex
*/
public function addUniqueConstraint( $type, $properties )
{
$tableNoQ = $this->esc( $type, TRUE );
$name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
$t = $this->getTable( $type );
$t['indexes'][$name] = array( 'name' => $name );
try {
$this->putTable( $t );
} catch( SQLException $e ) {
return FALSE;
}
return TRUE;
}
/**
* @see QueryWriter::sqlStateIn
*/
public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
{
$stateMap = array(
'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
);
if ( $state == 'HY000'
&& isset($extraDriverDetails[1])
&& $extraDriverDetails[1] == 1
&& ( in_array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, $list )
|| in_array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, $list )
)) {
return TRUE;
}
return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
}
/**
* Sets an SQL snippet to be used for the next queryRecord() operation.
* SQLite has no SELECT-FOR-UPDATE and filters this.
*
* @param string $sql SQL snippet to use in SELECT statement.
*
* return self
*/
public function setSQLSelectSnippet( $sqlSelectSnippet = '' ) {
if ( $sqlSelectSnippet === AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE) $sqlSelectSnippet = '';
$this->sqlSelectSnippet = $sqlSelectSnippet;
return $this;
}
/**
* @see QueryWriter::addIndex
*/
public function addIndex( $type, $name, $column )
{
$columns = $this->getColumns( $type );
if ( !isset( $columns[$column] ) ) return FALSE;
$table = $this->esc( $type );
$name = preg_replace( '/\W/', '', $name );
$column = $this->esc( $column, TRUE );
try {
$t = $this->getTable( $type );
$t['indexes'][$name] = array( 'name' => $column );
$this->putTable( $t );
return TRUE;
} catch( SQLException $exception ) {
return FALSE;
}
}
/**
* @see QueryWriter::wipe
*/
public function wipe( $type )
{
$table = $this->esc( $type );
$this->adapter->exec( "DELETE FROM $table " );
}
/**
* @see QueryWriter::addFK
*/
public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
{
return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
}
/**
* @see QueryWriter::wipeAll
*/
public function wipeAll()
{
if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
$this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
foreach ( $this->getTables() as $t ) {
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
}
$this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace RedBeanPHP;
/**
* R-Facade (for Composer)
*
* If you use Composer you don't use the rb.php file which
* has the R-facade, so here is a separate, namespaced R-facade for
* those that prefer this.
*
* An alternative option might be to alias RedBeanPHP/Facade.
*
* @file RedBeanPHP/R.php
* @author Simirimia
* @license BSD/GPLv2
*
*/
class R extends Facade
{
}

View File

@ -0,0 +1,20 @@
<?php
namespace RedBeanPHP;
/**
* RedBean\Exception Base.
* Represents the base class for RedBeanPHP\Exceptions.
*
* @file RedBeanPHP/Exception.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class RedException extends \Exception
{
}

View File

@ -0,0 +1,81 @@
<?php
namespace RedBeanPHP\RedException;
use RedBeanPHP\RedException as RedException;
/**
* SQL Exception.
* Represents a generic database exception independent of the underlying driver.
*
* @file RedBeanPHP/RedException/SQL.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SQL extends RedException
{
/**
* @var string
*/
private $sqlState;
/**
* @var array
*/
private $driverDetails = array();
/**
* @return array
*/
public function getDriverDetails()
{
return $this->driverDetails;
}
/**
* @param array $driverDetails
*/
public function setDriverDetails($driverDetails)
{
$this->driverDetails = $driverDetails;
}
/**
* Returns an ANSI-92 compliant SQL state.
*
* @return string
*/
public function getSQLState()
{
return $this->sqlState;
}
/**
* Returns the raw SQL STATE, possibly compliant with
* ANSI SQL error codes - but this depends on database driver.
*
* @param string $sqlState SQL state error code
*
* @return void
*/
public function setSQLState( $sqlState )
{
$this->sqlState = $sqlState;
}
/**
* To String prints both code and SQL state.
*
* @return string
*/
public function __toString()
{
return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
'trace: ' . $this->getTraceAsString();
}
}

View File

@ -0,0 +1,741 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\RedException\SQL as SQLException;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\Cursor as Cursor;
use RedBeanPHP\Cursor\NullCursor as NullCursor;
/**
* Abstract Repository.
*
* OODB manages two repositories, a fluid one that
* adjust the database schema on-the-fly to accomodate for
* new bean types (tables) and new properties (columns) and
* a frozen one for use in a production environment. OODB
* allows you to swap the repository instances using the freeze()
* method.
*
* @file RedBeanPHP/Repository.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
abstract class Repository
{
/**
* @var array
*/
protected $stash = NULL;
/*
* @var integer
*/
protected $nesting = 0;
/**
* @var DBAdapter
*/
protected $writer;
/**
* @var boolean
*/
protected $partialBeans = FALSE;
/**
* Toggles 'partial bean mode'. If this mode has been
* selected the repository will only update the fields of a bean that
* have been changed rather than the entire bean.
* Pass the value TRUE to select 'partial mode' for all beans.
* Pass the value FALSE to disable 'partial mode'.
* Pass an array of bean types if you wish to use partial mode only
* for some types.
* This method will return the previous value.
*
* @param boolean|array $yesNoBeans List of type names or 'all'
*
* @return mixed
*/
public function usePartialBeans( $yesNoBeans )
{
$oldValue = $this->partialBeans;
$this->partialBeans = $yesNoBeans;
return $oldValue;
}
/**
* Fully processes a bean and updates the associated records in the database.
* First the bean properties will be grouped as 'embedded' bean,
* addition, deleted 'trash can' or residue. Next, the different groups
* of beans will be processed accordingly and the reference bean (i.e.
* the one that was passed to the method as an argument) will be stored.
* Each type of list (own/shared) has 3 bean processors:
*
* - trashCanProcessor : removes the bean or breaks its association with the current bean
* - additionProcessor : associates the bean with the current one
* - residueProcessor : manages beans in lists that 'remain' but may need to be updated
*
* This method first groups the beans and then calls the
* internal processing methods.
*
* @param OODBBean $bean bean to process
*
* @return void
*/
protected function storeBeanWithLists( OODBBean $bean )
{
$sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
foreach ( $bean as $property => $value ) {
$value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value;
if ( $value instanceof OODBBean ) {
$this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
$bean->setMeta("sys.typeof.{$property}", $value->getMeta('type'));
} elseif ( is_array( $value ) ) {
foreach($value as &$item) {
$item = ( $item instanceof SimpleModel ) ? $item->unbox() : $item;
}
$originals = $bean->moveMeta( 'sys.shadow.' . $property, array() );
if ( strpos( $property, 'own' ) === 0 ) {
list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
$listName = lcfirst( substr( $property, 3 ) );
if ($bean->moveMeta( 'sys.exclusive-'. $listName ) ) {
OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) );
}
unset( $bean->$property );
} elseif ( strpos( $property, 'shared' ) === 0 ) {
list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
unset( $bean->$property );
}
}
}
$this->storeBean( $bean );
$this->processTrashcan( $bean, $ownTrashcan );
$this->processAdditions( $bean, $ownAdditions );
$this->processResidue( $ownresidue );
$this->processSharedTrashcan( $bean, $sharedTrashcan );
$this->processSharedAdditions( $bean, $sharedAdditions );
$this->processSharedResidue( $bean, $sharedresidue );
}
/**
* Process groups. Internal function. Processes different kind of groups for
* storage function. Given a list of original beans and a list of current beans,
* this function calculates which beans remain in the list (residue), which
* have been deleted (are in the trashcan) and which beans have been added
* (additions).
*
* @param array $originals originals
* @param array $current the current beans
* @param array $additions beans that have been added
* @param array $trashcan beans that have been deleted
* @param array $residue beans that have been left untouched
*
* @return array
*/
protected function processGroups( $originals, $current, $additions, $trashcan, $residue )
{
return array(
array_merge( $additions, array_diff( $current, $originals ) ),
array_merge( $trashcan, array_diff( $originals, $current ) ),
array_merge( $residue, array_intersect( $current, $originals ) )
);
}
/**
* Processes a list of beans from a bean.
* A bean may contain lists. This
* method handles shared addition lists; i.e.
* the $bean->sharedObject properties.
* Shared beans will be associated with eachother using the
* Association Manager.
*
* @param OODBBean $bean the bean
* @param array $sharedAdditions list with shared additions
*
* @return void
*/
protected function processSharedAdditions( $bean, $sharedAdditions )
{
foreach ( $sharedAdditions as $addition ) {
if ( $addition instanceof OODBBean ) {
$this->oodb->getAssociationManager()->associate( $addition, $bean );
} else {
throw new RedException( 'Array may only contain OODBBeans' );
}
}
}
/**
* Processes a list of beans from a bean.
* A bean may contain lists. This
* method handles own lists; i.e.
* the $bean->ownObject properties.
* A residue is a bean in an own-list that stays
* where it is. This method checks if there have been any
* modification to this bean, in that case
* the bean is stored once again, otherwise the bean will be left untouched.
*
* @param array $ownresidue list to process
*
* @return void
*/
protected function processResidue( $ownresidue )
{
foreach ( $ownresidue as $residue ) {
if ( $residue->getMeta( 'tainted' ) ) {
$this->store( $residue );
}
}
}
/**
* Processes a list of beans from a bean. A bean may contain lists. This
* method handles own lists; i.e. the $bean->ownObject properties.
* A trash can bean is a bean in an own-list that has been removed
* (when checked with the shadow). This method
* checks if the bean is also in the dependency list. If it is the bean will be removed.
* If not, the connection between the bean and the owner bean will be broken by
* setting the ID to NULL.
*
* @param OODBBean $bean bean to process
* @param array $ownTrashcan list to process
*
* @return void
*/
protected function processTrashcan( $bean, $ownTrashcan )
{
foreach ( $ownTrashcan as $trash ) {
$myFieldLink = $bean->getMeta( 'type' ) . '_id';
$alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
if ( $alias ) $myFieldLink = $alias . '_id';
if ( $trash->getMeta( 'sys.garbage' ) === TRUE ) {
$this->trash( $trash );
} else {
$trash->$myFieldLink = NULL;
$this->store( $trash );
}
}
}
/**
* Unassociates the list items in the trashcan.
* This bean processor processes the beans in the shared trash can.
* This group of beans has been deleted from a shared list.
* The affected beans will no longer be associated with the bean
* that contains the shared list.
*
* @param OODBBean $bean bean to process
* @param array $sharedTrashcan list to process
*
* @return void
*/
protected function processSharedTrashcan( $bean, $sharedTrashcan )
{
foreach ( $sharedTrashcan as $trash ) {
$this->oodb->getAssociationManager()->unassociate( $trash, $bean );
}
}
/**
* Stores all the beans in the residue group.
* This bean processor processes the beans in the shared residue
* group. This group of beans 'remains' in the list but might need
* to be updated or synced. The affected beans will be stored
* to perform the required database queries.
*
* @param OODBBean $bean bean to process
* @param array $sharedresidue list to process
*
* @return void
*/
protected function processSharedResidue( $bean, $sharedresidue )
{
foreach ( $sharedresidue as $residue ) {
$this->store( $residue );
}
}
/**
* Determines whether the bean has 'loaded lists' or
* 'loaded embedded beans' that need to be processed
* by the store() method.
*
* @param OODBBean $bean bean to be examined
*
* @return boolean
*/
protected function hasListsOrObjects( OODBBean $bean )
{
$processLists = FALSE;
foreach ( $bean as $value ) {
if ( is_array( $value ) || is_object( $value ) ) {
$processLists = TRUE;
break;
}
}
return $processLists;
}
/**
* Converts an embedded bean to an ID, removes the bean property and
* stores the bean in the embedded beans array. The id will be
* assigned to the link field property, i.e. 'bean_id'.
*
* @param array $embeddedBeans destination array for embedded bean
* @param OODBBean $bean target bean to process
* @param string $property property that contains the embedded bean
* @param OODBBean $value embedded bean itself
*
* @return void
*/
protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
{
$linkField = $property . '_id';
if ( !$value->id || $value->getMeta( 'tainted' ) ) {
$this->store( $value );
}
$id = $value->id;
if ($bean->$linkField != $id) $bean->$linkField = $id;
$bean->setMeta( 'cast.' . $linkField, 'id' );
$embeddedBeans[$linkField] = $value;
unset( $bean->$property );
}
/**
* Constructor, requires a query writer and OODB.
* Creates a new instance of the bean respository class.
*
* @param OODB $oodb instance of object database
* @param QueryWriter $writer the Query Writer to use for this repository
*
* @return void
*/
public function __construct( OODB $oodb, QueryWriter $writer )
{
$this->writer = $writer;
$this->oodb = $oodb;
}
/**
* Checks whether a OODBBean bean is valid.
* If the type is not valid or the ID is not valid it will
* throw an exception: Security. To be valid a bean
* must abide to the following rules:
*
* - It must have an primary key id property named: id
* - It must have a type
* - The type must conform to the RedBeanPHP naming policy
* - All properties must be valid
* - All values must be valid
*
* @param OODBBean $bean the bean that needs to be checked
*
* @return void
*/
public function check( OODBBean $bean )
{
//Is all meta information present?
if ( !isset( $bean->id ) ) {
throw new RedException( 'Bean has incomplete Meta Information id ' );
}
if ( !( $bean->getMeta( 'type' ) ) ) {
throw new RedException( 'Bean has incomplete Meta Information II' );
}
//Pattern of allowed characters
$pattern = '/[^a-z0-9_]/i';
//Does the type contain invalid characters?
if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
throw new RedException( 'Bean Type is invalid' );
}
//Are the properties and values valid?
foreach ( $bean as $prop => $value ) {
if (
is_array( $value )
|| ( is_object( $value ) )
) {
throw new RedException( "Invalid Bean value: property $prop" );
} else if (
strlen( $prop ) < 1
|| preg_match( $pattern, $prop )
) {
throw new RedException( "Invalid Bean property: property $prop" );
}
}
}
/**
* Dispenses a new bean (a OODBBean Bean Object)
* of the specified type. Always
* use this function to get an empty bean object. Never
* instantiate a OODBBean yourself because it needs
* to be configured before you can use it with RedBean. This
* function applies the appropriate initialization /
* configuration for you.
*
* To use a different class for beans (instead of OODBBean) set:
* REDBEAN_OODBBEAN_CLASS to the name of the class to be used.
*
* @param string $type type of bean you want to dispense
* @param int $number number of beans you would like to get
* @param boolean $alwaysReturnArray if TRUE always returns the result as an array
*
* @return OODBBean
*/
public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
{
$OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
$beans = array();
for ( $i = 0; $i < $number; $i++ ) {
$bean = new $OODBBEAN;
$bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
$this->check( $bean );
$this->oodb->signal( 'dispense', $bean );
$beans[] = $bean;
}
return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
}
/**
* Searches the database for a bean that matches conditions $conditions and sql $addSQL
* and returns an array containing all the beans that have been found.
*
* Conditions need to take form:
*
* <code>
* array(
* 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
* 'PROPERTY' => array( POSSIBLE VALUES... )
* );
* </code>
*
* All conditions are glued together using the AND-operator, while all value lists
* are glued using IN-operators thus acting as OR-conditions.
*
* Note that you can use property names; the columns will be extracted using the
* appropriate bean formatter.
*
* @param string $type type of beans you are looking for
* @param array $conditions list of conditions
* @param string $sql SQL to be used in query
* @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
*
* @return array
*/
public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
{
//for backward compatibility, allow mismatch arguments:
if ( is_array( $sql ) ) {
if ( isset( $sql[1] ) ) {
$bindings = $sql[1];
}
$sql = $sql[0];
}
try {
$beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
return $beans;
} catch ( SQLException $exception ) {
$this->handleException( $exception );
}
return array();
}
/**
* Finds a BeanCollection.
* Given a type, an SQL snippet and optionally some parameter bindings
* this methods returns a BeanCollection for your query.
*
* The BeanCollection represents a collection of beans and
* makes it possible to use database cursors. The BeanCollection
* has a method next() to obtain the first, next and last bean
* in the collection. The BeanCollection does not implement the array
* interface nor does it try to act like an array because it cannot go
* backward or rewind itself.
*
* @param string $type type of beans you are looking for
* @param string $sql SQL to be used in query
* @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
*
* @return BeanCollection
*/
public function findCollection( $type, $sql, $bindings = array() )
{
try {
$cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings );
return new BeanCollection( $type, $this, $cursor );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
}
return new BeanCollection( $type, $this, new NullCursor );
}
/**
* Stores a bean in the database. This method takes a
* OODBBean Bean Object $bean and stores it
* in the database. If the database schema is not compatible
* with this bean and RedBean runs in fluid mode the schema
* will be altered to store the bean correctly.
* If the database schema is not compatible with this bean and
* RedBean runs in frozen mode it will throw an exception.
* This function returns the primary key ID of the inserted
* bean.
*
* The return value is an integer if possible. If it is not possible to
* represent the value as an integer a string will be returned. We use
* explicit casts instead of functions to preserve performance
* (0.13 vs 0.28 for 10000 iterations on Core i3).
*
* @param OODBBean|SimpleModel $bean bean to store
*
* @return integer|string
*/
public function store( $bean )
{
$processLists = $this->hasListsOrObjects( $bean );
if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
return $bean->getID(); //bail out!
}
$this->oodb->signal( 'update', $bean );
$processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
if ( $processLists ) {
$this->storeBeanWithLists( $bean );
} else {
$this->storeBean( $bean );
}
$this->oodb->signal( 'after_update', $bean );
return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
}
/**
* Returns an array of beans. Pass a type and a series of ids and
* this method will bring you the corresponding beans.
*
* important note: Because this method loads beans using the load()
* function (but faster) it will return empty beans with ID 0 for
* every bean that could not be located. The resulting beans will have the
* passed IDs as their keys.
*
* @param string $type type of beans
* @param array $ids ids to load
*
* @return array
*/
public function batch( $type, $ids )
{
if ( !$ids ) {
return array();
}
$collection = array();
try {
$rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
} catch ( SQLException $e ) {
$this->handleException( $e );
$rows = FALSE;
}
$this->stash[$this->nesting] = array();
if ( !$rows ) {
return array();
}
foreach ( $rows as $row ) {
$this->stash[$this->nesting][$row['id']] = $row;
}
foreach ( $ids as $id ) {
$collection[$id] = $this->load( $type, $id );
}
$this->stash[$this->nesting] = NULL;
return $collection;
}
/**
* This is a convenience method; it converts database rows
* (arrays) into beans. Given a type and a set of rows this method
* will return an array of beans of the specified type loaded with
* the data fields provided by the result set from the database.
*
* New in 4.3.2: meta mask. The meta mask is a special mask to send
* data from raw result rows to the meta store of the bean. This is
* useful for bundling additional information with custom queries.
* Values of every column whos name starts with $mask will be
* transferred to the meta section of the bean under key 'data.bundle'.
*
* @param string $type type of beans you would like to have
* @param array $rows rows from the database result
* @param string $mask meta mask to apply (optional)
*
* @return array
*/
public function convertToBeans( $type, $rows, $mask = '__meta' )
{
$masktype = gettype( $mask );
switch ( $masktype ) {
case 'string':
break;
case 'array':
$maskflip = array();
foreach ( $mask as $m ) {
if ( !is_string( $m ) ) {
$mask = NULL;
$masktype = 'NULL';
break 2;
}
$maskflip[$m] = TRUE;
}
$mask = $maskflip;
break;
default:
$mask = NULL;
$masktype = 'NULL';
}
$collection = array();
$this->stash[$this->nesting] = array();
foreach ( $rows as $row ) {
if ( $mask !== NULL ) {
$meta = array();
foreach( $row as $key => $value ) {
if ( $masktype === 'string' ) {
if ( strpos( $key, $mask ) === 0 ) {
unset( $row[$key] );
$meta[$key] = $value;
}
} elseif ( $masktype === 'array' ) {
if ( isset( $mask[$key] ) ) {
unset( $row[$key] );
$meta[$key] = $value;
}
}
}
}
$id = $row['id'];
$this->stash[$this->nesting][$id] = $row;
$collection[$id] = $this->load( $type, $id );
if ( $mask !== NULL ) {
$collection[$id]->setMeta( 'data.bundle', $meta );
}
}
$this->stash[$this->nesting] = NULL;
return $collection;
}
/**
* Counts the number of beans of type $type.
* This method accepts a second argument to modify the count-query.
* A third argument can be used to provide bindings for the SQL snippet.
*
* @param string $type type of bean we are looking for
* @param string $addSQL additional SQL snippet
* @param array $bindings parameters to bind to SQL
*
* @return integer
*/
public function count( $type, $addSQL = '', $bindings = array() )
{
$type = AQueryWriter::camelsSnake( $type );
if ( count( explode( '_', $type ) ) > 2 ) {
throw new RedException( 'Invalid type for count.' );
}
try {
$count = (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
$count = 0;
}
return $count;
}
/**
* Removes a bean from the database.
* This function will remove the specified OODBBean
* Bean Object from the database.
*
* @param OODBBean|SimpleModel $bean bean you want to remove from database
*
* @return void
*/
public function trash( $bean )
{
$this->oodb->signal( 'delete', $bean );
foreach ( $bean as $property => $value ) {
if ( $value instanceof OODBBean ) {
unset( $bean->$property );
}
if ( is_array( $value ) ) {
if ( strpos( $property, 'own' ) === 0 ) {
unset( $bean->$property );
} elseif ( strpos( $property, 'shared' ) === 0 ) {
unset( $bean->$property );
}
}
}
try {
$deleted = $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL );
} catch ( SQLException $exception ) {
$this->handleException( $exception );
}
$bean->id = 0;
$this->oodb->signal( 'after_delete', $bean );
return isset($deleted) ? $deleted : 0;
}
/**
* Checks whether the specified table already exists in the database.
* Not part of the Object Database interface!
*
* @deprecated Use AQueryWriter::typeExists() instead.
*
* @param string $table table name
*
* @return boolean
*/
public function tableExists( $table )
{
return $this->writer->tableExists( $table );
}
/**
* Trash all beans of a given type.
* Wipes an entire type of bean. After this operation there
* will be no beans left of the specified type.
* This method will ignore exceptions caused by database
* tables that do not exist.
*
* @param string $type type of bean you wish to delete all instances of
*
* @return boolean
*/
public function wipe( $type )
{
try {
$this->writer->wipe( $type );
return TRUE;
} catch ( SQLException $exception ) {
if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ), $exception->getDriverDetails() ) ) {
throw $exception;
}
return FALSE;
}
}
}

View File

@ -0,0 +1,323 @@
<?php
namespace RedBeanPHP\Repository;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\RedException\SQL as SQLException;
use RedBeanPHP\Repository as Repository;
/**
* Fluid Repository.
* OODB manages two repositories, a fluid one that
* adjust the database schema on-the-fly to accomodate for
* new bean types (tables) and new properties (columns) and
* a frozen one for use in a production environment. OODB
* allows you to swap the repository instances using the freeze()
* method.
*
* @file RedBeanPHP/Repository/Fluid.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Fluid extends Repository
{
/**
* Figures out the desired type given the cast string ID.
* Given a cast ID, this method will return the associated
* type (INT(10) or VARCHAR for instance). The returned type
* can be processed by the Query Writer to build the specified
* column for you in the database. The Cast ID is actually just
* a superset of the QueryWriter types. In addition to default
* Query Writer column types you can pass the following 'cast types':
* 'id' and 'string'. These will map to Query Writer specific
* column types (probably INT and VARCHAR).
*
* @param string $cast cast identifier
*
* @return integer
*/
private function getTypeFromCast( $cast )
{
if ( $cast == 'string' ) {
$typeno = $this->writer->scanType( 'STRING' );
} elseif ( $cast == 'id' ) {
$typeno = $this->writer->getTypeForID();
} elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
$typeno = $this->writer->sqltype_typeno[$cast];
} else {
throw new RedException( 'Invalid Cast' );
}
return $typeno;
}
/**
* Orders the Query Writer to create a table if it does not exist already and
* adds a note in the build report about the creation.
*
* @param OODBBean $bean bean to update report of
* @param string $table table to check and create if not exists
*
* @return void
*/
private function createTableIfNotExists( OODBBean $bean, $table )
{
//Does table exist? If not, create
if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
$this->writer->createTable( $table );
$bean->setMeta( 'buildreport.flags.created', TRUE );
}
}
/**
* Modifies the table to fit the bean data.
* Given a property and a value and the bean, this method will
* adjust the table structure to fit the requirements of the property and value.
* This may include adding a new column or widening an existing column to hold a larger
* or different kind of value. This method employs the writer to adjust the table
* structure in the database. Schema updates are recorded in meta properties of the bean.
*
* This method will also apply indexes, unique constraints and foreign keys.
*
* @param OODBBean $bean bean to get cast data from and store meta in
* @param string $property property to store
* @param mixed $value value to store
*
* @return void
*/
private function modifySchema( OODBBean $bean, $property, $value, &$columns = NULL )
{
$doFKStuff = FALSE;
$table = $bean->getMeta( 'type' );
if ($columns === NULL) {
$columns = $this->writer->getColumns( $table );
}
$columnNoQ = $this->writer->esc( $property, TRUE );
if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
$cast = $bean->getMeta( "cast.$property" );
$typeno = $this->getTypeFromCast( $cast );
} else {
$cast = FALSE;
$typeno = $this->writer->scanType( $value, TRUE );
}
if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
if ( !$cast ) { //rescan without taking into account special types >80
$typeno = $this->writer->scanType( $value, FALSE );
}
$sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
if ( $typeno > $sqlt ) { //no, we have to widen the database column type
$this->writer->widenColumn( $table, $property, $typeno );
$bean->setMeta( 'buildreport.flags.widen', TRUE );
$doFKStuff = TRUE;
}
} else {
$this->writer->addColumn( $table, $property, $typeno );
$bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
$doFKStuff = TRUE;
}
if ($doFKStuff) {
if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
$destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
$indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
$this->writer->addIndex($table, $indexName, $columnNoQ);
$typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
$isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
//Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
$isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
$result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
//If this is a link bean and all unique columns have been added already, then apply unique constraint
if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
$this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
$bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
}
}
}
}
}
/**
* Part of the store() functionality.
* Handles all new additions after the bean has been saved.
* Stores addition bean in own-list, extracts the id and
* adds a foreign key. Also adds a constraint in case the type is
* in the dependent list.
*
* Note that this method raises a custom exception if the bean
* is not an instance of OODBBean. Therefore it does not use
* a type hint. This allows the user to take action in case
* invalid objects are passed in the list.
*
* @param OODBBean $bean bean to process
* @param array $ownAdditions list of addition beans in own-list
*
* @return void
*/
protected function processAdditions( $bean, $ownAdditions )
{
$beanType = $bean->getMeta( 'type' );
foreach ( $ownAdditions as $addition ) {
if ( $addition instanceof OODBBean ) {
$myFieldLink = $beanType . '_id';
$alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
if ( $alias ) $myFieldLink = $alias . '_id';
$addition->$myFieldLink = $bean->id;
$addition->setMeta( 'cast.' . $myFieldLink, 'id' );
if ($alias) {
$addition->setMeta( "sys.typeof.{$alias}", $beanType );
} else {
$addition->setMeta( "sys.typeof.{$beanType}", $beanType );
}
$this->store( $addition );
} else {
throw new RedException( 'Array may only contain OODBBeans' );
}
}
}
/**
* Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
* method. When all lists and embedded beans (parent objects) have been processed and
* removed from the original bean the bean is passed to this method to be stored
* in the database.
*
* @param OODBBean $bean the clean bean
*
* @return void
*/
protected function storeBean( OODBBean $bean )
{
if ( $bean->getMeta( 'changed' ) ) {
$this->check( $bean );
$table = $bean->getMeta( 'type' );
$this->createTableIfNotExists( $bean, $table );
$updateValues = array();
$partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
if ( $partial ) {
$mask = $bean->getMeta( 'changelist' );
$bean->setMeta( 'changelist', array() );
}
$columnCache = NULL;
foreach ( $bean as $property => $value ) {
if ( $partial && !in_array( $property, $mask ) ) continue;
if ( $property !== 'id' ) {
$this->modifySchema( $bean, $property, $value, $columnCache );
}
if ( $property !== 'id' ) {
$updateValues[] = array( 'property' => $property, 'value' => $value );
}
}
$bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
$bean->setMeta( 'changed', FALSE );
}
$bean->setMeta( 'tainted', FALSE );
}
/**
* Exception handler.
* Fluid and Frozen mode have different ways of handling
* exceptions. Fluid mode (using the fluid repository) ignores
* exceptions caused by the following:
*
* - missing tables
* - missing column
*
* In these situations, the repository will behave as if
* no beans could be found. This is because in fluid mode
* it might happen to query a table or column that has not been
* created yet. In frozen mode, this is not supposed to happen
* and the corresponding exceptions will be thrown.
*
* @param \Exception $exception exception
*
* @return void
*/
protected function handleException( \Exception $exception )
{
if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
array(
QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
$exception->getDriverDetails() )
) {
throw $exception;
}
}
/**
* Loads a bean from the object database.
* It searches for a OODBBean Bean Object in the
* database. It does not matter how this bean has been stored.
* RedBean uses the primary key ID $id and the string $type
* to find the bean. The $type specifies what kind of bean you
* are looking for; this is the same type as used with the
* dispense() function. If RedBean finds the bean it will return
* the OODB Bean object; if it cannot find the bean
* RedBean will return a new bean of type $type and with
* primary key ID 0. In the latter case it acts basically the
* same as dispense().
*
* Important note:
* If the bean cannot be found in the database a new bean of
* the specified type will be generated and returned.
*
* @param string $type type of bean you want to load
* @param integer $id ID of the bean you want to load
*
* @return OODBBean
*/
public function load( $type, $id )
{
$rows = array();
$bean = $this->dispense( $type );
if ( isset( $this->stash[$this->nesting][$id] ) ) {
$row = $this->stash[$this->nesting][$id];
} else {
try {
$rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
} catch ( SQLException $exception ) {
if (
$this->writer->sqlStateIn(
$exception->getSQLState(),
array(
QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
),
$exception->getDriverDetails()
)
) {
$rows = array();
} else {
throw $exception;
}
}
if ( !count( $rows ) ) {
return $bean;
}
$row = array_pop( $rows );
}
$bean->importRow( $row );
$this->nesting++;
$this->oodb->signal( 'open', $bean );
$this->nesting--;
return $bean->setMeta( 'tainted', FALSE );
}
}

View File

@ -0,0 +1,176 @@
<?php
namespace RedBeanPHP\Repository;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\RedException\SQL as SQLException;
use RedBeanPHP\Repository as Repository;
/**
* Frozen Repository.
* OODB manages two repositories, a fluid one that
* adjust the database schema on-the-fly to accomodate for
* new bean types (tables) and new properties (columns) and
* a frozen one for use in a production environment. OODB
* allows you to swap the repository instances using the freeze()
* method.
*
* @file RedBeanPHP/Repository/Frozen.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Frozen extends Repository
{
/**
* Exception handler.
* Fluid and Frozen mode have different ways of handling
* exceptions. Fluid mode (using the fluid repository) ignores
* exceptions caused by the following:
*
* - missing tables
* - missing column
*
* In these situations, the repository will behave as if
* no beans could be found. This is because in fluid mode
* it might happen to query a table or column that has not been
* created yet. In frozen mode, this is not supposed to happen
* and the corresponding exceptions will be thrown.
*
* @param \Exception $exception exception
*
* @return void
*/
protected function handleException( \Exception $exception )
{
throw $exception;
}
/**
* Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
* method. When all lists and embedded beans (parent objects) have been processed and
* removed from the original bean the bean is passed to this method to be stored
* in the database.
*
* @param OODBBean $bean the clean bean
*
* @return void
*/
protected function storeBean( OODBBean $bean )
{
if ( $bean->getMeta( 'changed' ) ) {
list( $properties, $table ) = $bean->getPropertiesAndType();
$id = $properties['id'];
unset($properties['id']);
$updateValues = array();
$k1 = 'property';
$k2 = 'value';
$partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
if ( $partial ) {
$mask = $bean->getMeta( 'changelist' );
$bean->setMeta( 'changelist', array() );
}
foreach( $properties as $key => $value ) {
if ( $partial && !in_array( $key, $mask ) ) continue;
$updateValues[] = array( $k1 => $key, $k2 => $value );
}
$bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
$bean->setMeta( 'changed', FALSE );
}
$bean->setMeta( 'tainted', FALSE );
}
/**
* Part of the store() functionality.
* Handles all new additions after the bean has been saved.
* Stores addition bean in own-list, extracts the id and
* adds a foreign key. Also adds a constraint in case the type is
* in the dependent list.
*
* Note that this method raises a custom exception if the bean
* is not an instance of OODBBean. Therefore it does not use
* a type hint. This allows the user to take action in case
* invalid objects are passed in the list.
*
* @param OODBBean $bean bean to process
* @param array $ownAdditions list of addition beans in own-list
*
* @return void
* @throws RedException
*/
protected function processAdditions( $bean, $ownAdditions )
{
$beanType = $bean->getMeta( 'type' );
$cachedIndex = array();
foreach ( $ownAdditions as $addition ) {
if ( $addition instanceof OODBBean ) {
$myFieldLink = $beanType . '_id';
$alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
if ( $alias ) $myFieldLink = $alias . '_id';
$addition->$myFieldLink = $bean->id;
$addition->setMeta( 'cast.' . $myFieldLink, 'id' );
$this->store( $addition );
} else {
throw new RedException( 'Array may only contain OODBBeans' );
}
}
}
/**
* Loads a bean from the object database.
* It searches for a OODBBean Bean Object in the
* database. It does not matter how this bean has been stored.
* RedBean uses the primary key ID $id and the string $type
* to find the bean. The $type specifies what kind of bean you
* are looking for; this is the same type as used with the
* dispense() function. If RedBean finds the bean it will return
* the OODB Bean object; if it cannot find the bean
* RedBean will return a new bean of type $type and with
* primary key ID 0. In the latter case it acts basically the
* same as dispense().
*
* Important note:
* If the bean cannot be found in the database a new bean of
* the specified type will be generated and returned.
*
* @param string $type type of bean you want to load
* @param integer $id ID of the bean you want to load
*
* @return OODBBean
* @throws SQLException
*/
public function load( $type, $id )
{
$rows = array();
$bean = $this->dispense( $type );
if ( isset( $this->stash[$this->nesting][$id] ) ) {
$row = $this->stash[$this->nesting][$id];
} else {
$rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
if ( !count( $rows ) ) {
return $bean;
}
$row = array_pop( $rows );
}
$bean->importRow( $row );
$this->nesting++;
$this->oodb->signal( 'open', $bean );
$this->nesting--;
return $bean->setMeta( 'tainted', FALSE );
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\OODBBean as OODBBean;
/**
* SimpleModel
* Base Model For All RedBeanPHP Models using FUSE.
*
* RedBeanPHP FUSE is a mechanism to connect beans to posthoc
* models. Models are connected to beans by naming conventions.
* Actions on beans will result in actions on models.
*
* @file RedBeanPHP/SimpleModel.php
* @author Gabor de Mooij and the RedBeanPHP Team
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SimpleModel
{
/**
* @var OODBBean
*/
protected $bean;
/**
* Used by FUSE: the ModelHelper class to connect a bean to a model.
* This method loads a bean in the model.
*
* @param OODBBean $bean bean to load
*
* @return void
*/
public function loadBean( OODBBean $bean )
{
$this->bean = $bean;
}
/**
* Magic Getter to make the bean properties available from
* the $this-scope.
*
* @note this method returns a value, not a reference!
* To obtain a reference unbox the bean first!
*
* @param string $prop property to get
*
* @return mixed
*/
public function __get( $prop )
{
return $this->bean->$prop;
}
/**
* Magic Setter.
* Sets the value directly as a bean property.
*
* @param string $prop property to set value of
* @param mixed $value value to set
*
* @return void
*/
public function __set( $prop, $value )
{
$this->bean->$prop = $value;
}
/**
* Isset implementation.
* Implements the isset function for array-like access.
*
* @param string $key key to check
*
* @return boolean
*/
public function __isset( $key )
{
return isset( $this->bean->$key );
}
/**
* Box the bean using the current model.
* This method wraps the current bean in this model.
* This method can be reached using FUSE through a simple
* OODBBean. The method returns a RedBeanPHP Simple Model.
* This is useful if you would like to rely on PHP type hinting.
* You can box your beans before passing them to functions or methods
* with typed parameters.
*
* Note about beans vs models:
* Use unbox to obtain the bean powering the model. If you want to use bean functionality,
* you should -always- unbox first. While some functionality (like magic get/set) is
* available in the model, this is just read-only. To use a model as a typical RedBean
* OODBBean you should always unbox the model to a bean. Models are meant to
* expose only domain logic added by the developer (business logic, no ORM logic).
*
* @return SimpleModel
*/
public function box()
{
return $this;
}
/**
* Unbox the bean from the model.
* This method returns the bean inside the model.
*
* Note about beans vs models:
* Use unbox to obtain the bean powering the model. If you want to use bean functionality,
* you should -always- unbox first. While some functionality (like magic get/set) is
* available in the model, this is just read-only. To use a model as a typical RedBean
* OODBBean you should always unbox the model to a bean. Models are meant to
* expose only domain logic added by the developer (business logic, no ORM logic).
*
* @return OODBBean
*/
public function unbox()
{
return $this->bean;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\Observer as Observer;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\Observable as Observable;
/**
* RedBean Model Helper.
*
* Connects beans to models.
* This is the core of so-called FUSE.
*
* @file RedBeanPHP/ModelHelper.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class SimpleModelHelper implements Observer
{
/**
* Gets notified by an observable.
* This method decouples the FUSE system from the actual beans.
* If a FUSE event happens 'update', this method will attempt to
* invoke the corresponding method on the bean.
*
* @param string $eventName i.e. 'delete', 'after_delete'
* @param OODBean $bean affected bean
*
* @return void
*/
public function onEvent( $eventName, $bean )
{
$bean->$eventName();
}
/**
* Attaches the FUSE event listeners. Now the Model Helper will listen for
* CRUD events. If a CRUD event occurs it will send a signal to the model
* that belongs to the CRUD bean and this model will take over control from
* there. This method will attach the following event listeners to the observable:
*
* - 'update' (gets called by R::store, before the records gets inserted / updated)
* - 'after_update' (gets called by R::store, after the records have been inserted / updated)
* - 'open' (gets called by R::load, after the record has been retrieved)
* - 'delete' (gets called by R::trash, before deletion of record)
* - 'after_delete' (gets called by R::trash, after deletion)
* - 'dispense' (gets called by R::dispense)
*
* For every event type, this method will register this helper as a listener.
* The observable will notify the listener (this object) with the event ID and the
* affected bean. This helper will then process the event (onEvent) by invoking
* the event on the bean. If a bean offers a method with the same name as the
* event ID, this method will be invoked.
*
* @param Observable $observable object to observe
*
* @return void
*/
public function attachEventListeners( Observable $observable )
{
foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $eventID ) {
$observable->addEventListener( $eventID, $this );
}
}
}

View File

@ -0,0 +1,370 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\OODBBean as OODBBean;
/**
* RedBeanPHP Tag Manager.
*
* The tag manager offers an easy way to quickly implement basic tagging
* functionality.
*
* Provides methods to tag beans and perform tag-based searches in the
* bean database.
*
* @file RedBeanPHP/TagManager.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class TagManager
{
/**
* @var ToolBox
*/
protected $toolbox;
/**
* @var AssociationManager
*/
protected $associationManager;
/**
* @var OODBBean
*/
protected $redbean;
/**
* Checks if the argument is a comma separated string, in this case
* it will split the string into words and return an array instead.
* In case of an array the argument will be returned 'as is'.
*
* @param array|string $tagList list of tags
*
* @return array
*/
private function extractTagsIfNeeded( $tagList )
{
if ( $tagList !== FALSE && !is_array( $tagList ) ) {
$tags = explode( ',', (string) $tagList );
} else {
$tags = $tagList;
}
return $tags;
}
/**
* Finds a tag bean by it's title.
* Internal method.
*
* @param string $title title to search for
*
* @return OODBBean
*/
protected function findTagByTitle( $title )
{
$beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
if ( $beans ) {
$bean = reset( $beans );
return $bean;
}
return NULL;
}
/**
* Constructor.
* The tag manager offers an easy way to quickly implement basic tagging
* functionality.
*
* @param ToolBox $toolbox toolbox object
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
$this->redbean = $toolbox->getRedBean();
$this->associationManager = $this->redbean->getAssociationManager();
}
/**
* Tests whether a bean has been associated with one ore more
* of the listed tags. If the third parameter is TRUE this method
* will return TRUE only if all tags that have been specified are indeed
* associated with the given bean, otherwise FALSE.
* If the third parameter is FALSE this
* method will return TRUE if one of the tags matches, FALSE if none
* match.
*
* Tag list can be either an array with tag names or a comma separated list
* of tag names.
*
* Usage:
*
* <code>
* R::hasTag( $blog, 'horror,movie', TRUE );
* </code>
*
* The example above returns TRUE if the $blog bean has been tagged
* as BOTH horror and movie. If the post has only been tagged as 'movie'
* or 'horror' this operation will return FALSE because the third parameter
* has been set to TRUE.
*
* @param OODBBean $bean bean to check for tags
* @param array|string $tags list of tags
* @param boolean $all whether they must all match or just some
*
* @return boolean
*/
public function hasTag( $bean, $tags, $all = FALSE )
{
$foundtags = $this->tag( $bean );
$tags = $this->extractTagsIfNeeded( $tags );
$same = array_intersect( $tags, $foundtags );
if ( $all ) {
return ( implode( ',', $same ) === implode( ',', $tags ) );
}
return (bool) ( count( $same ) > 0 );
}
/**
* Removes all specified tags from the bean. The tags specified in
* the second parameter will no longer be associated with the bean.
*
* Tag list can be either an array with tag names or a comma separated list
* of tag names.
*
* Usage:
*
* <code>
* R::untag( $blog, 'smart,interesting' );
* </code>
*
* In the example above, the $blog bean will no longer
* be associated with the tags 'smart' and 'interesting'.
*
* @param OODBBean $bean tagged bean
* @param array $tagList list of tags (names)
*
* @return void
*/
public function untag( $bean, $tagList )
{
$tags = $this->extractTagsIfNeeded( $tagList );
foreach ( $tags as $tag ) {
if ( $t = $this->findTagByTitle( $tag ) ) {
$this->associationManager->unassociate( $bean, $t );
}
}
}
/**
* Part of RedBeanPHP Tagging API.
* Tags a bean or returns tags associated with a bean.
* If $tagList is NULL or omitted this method will return a
* comma separated list of tags associated with the bean provided.
* If $tagList is a comma separated list (string) of tags all tags will
* be associated with the bean.
* You may also pass an array instead of a string.
*
* Usage:
*
* <code>
* R::tag( $meal, "TexMex,Mexican" );
* $tags = R::tag( $meal );
* </code>
*
* The first line in the example above will tag the $meal
* as 'TexMex' and 'Mexican Cuisine'. The second line will
* retrieve all tags attached to the meal object.
*
* @param OODBBean $bean bean to tag
* @param mixed $tagList tags to attach to the specified bean
*
* @return string
*/
public function tag( OODBBean $bean, $tagList = NULL )
{
if ( is_null( $tagList ) ) {
$tags = $bean->sharedTag;
$foundTags = array();
foreach ( $tags as $tag ) {
$foundTags[] = $tag->title;
}
return $foundTags;
}
$this->associationManager->clearRelations( $bean, 'tag' );
$this->addTags( $bean, $tagList );
return $tagList;
}
/**
* Part of RedBeanPHP Tagging API.
* Adds tags to a bean.
* If $tagList is a comma separated list of tags all tags will
* be associated with the bean.
* You may also pass an array instead of a string.
*
* Usage:
*
* <code>
* R::addTags( $blog, ["halloween"] );
* </code>
*
* The example adds the tag 'halloween' to the $blog
* bean.
*
* @param OODBBean $bean bean to tag
* @param array $tagList list of tags to add to bean
*
* @return void
*/
public function addTags( OODBBean $bean, $tagList )
{
$tags = $this->extractTagsIfNeeded( $tagList );
if ( $tagList === FALSE ) {
return;
}
foreach ( $tags as $tag ) {
if ( !$t = $this->findTagByTitle( $tag ) ) {
$t = $this->redbean->dispense( 'tag' );
$t->title = $tag;
$this->redbean->store( $t );
}
$this->associationManager->associate( $bean, $t );
}
}
/**
* Returns all beans that have been tagged with one or more
* of the specified tags.
*
* Tag list can be either an array with tag names or a comma separated list
* of tag names.
*
* Usage:
*
* <code>
* $watchList = R::tagged(
* 'movie',
* 'horror,gothic',
* ' ORDER BY movie.title DESC LIMIT ?',
* [ 10 ]
* );
* </code>
*
* The example uses R::tagged() to find all movies that have been
* tagged as 'horror' or 'gothic', order them by title and limit
* the number of movies to be returned to 10.
*
* @param string $beanType type of bean you are looking for
* @param array|string $tagList list of tags to match
* @param string $sql additional SQL (use only for pagination)
* @param array $bindings bindings
*
* @return array
*/
public function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
{
$tags = $this->extractTagsIfNeeded( $tagList );
$records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, FALSE, $sql, $bindings );
return $this->redbean->convertToBeans( $beanType, $records );
}
/**
* Returns all beans that have been tagged with ALL of the tags given.
* This method works the same as R::tagged() except that this method only returns
* beans that have been tagged with all the specified labels.
*
* Tag list can be either an array with tag names or a comma separated list
* of tag names.
*
* Usage:
*
* <code>
* $watchList = R::taggedAll(
* 'movie',
* [ 'gothic', 'short' ],
* ' ORDER BY movie.id DESC LIMIT ? ',
* [ 4 ]
* );
* </code>
*
* The example above returns at most 4 movies (due to the LIMIT clause in the SQL
* Query Snippet) that have been tagged as BOTH 'short' AND 'gothic'.
*
* @param string $beanType type of bean you are looking for
* @param array|string $tagList list of tags to match
* @param string $sql additional sql snippet
* @param array $bindings bindings
*
* @return array
*/
public function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
{
$tags = $this->extractTagsIfNeeded( $tagList );
$records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, TRUE, $sql, $bindings );
return $this->redbean->convertToBeans( $beanType, $records );
}
/**
* Like taggedAll() but only counts.
*
* @see taggedAll
*
* @param string $beanType type of bean you are looking for
* @param array|string $tagList list of tags to match
* @param string $sql additional sql snippet
* @param array $bindings bindings
*
* @return integer
*/
public function countTaggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
{
$tags = $this->extractTagsIfNeeded( $tagList );
return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, TRUE, $sql, $bindings );
}
/**
* Like tagged() but only counts.
*
* @see tagged
*
* @param string $beanType type of bean you are looking for
* @param array|string $tagList list of tags to match
* @param string $sql additional sql snippet
* @param array $bindings bindings
*
* @return integer
*/
public function countTagged( $beanType, $tagList, $sql = '', $bindings = array() )
{
$tags = $this->extractTagsIfNeeded( $tagList );
return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, FALSE, $sql, $bindings );
}
}

View File

@ -0,0 +1,167 @@
<?php
namespace RedBeanPHP;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\Adapter as Adapter;
/**
* ToolBox.
*
* The toolbox is an integral part of RedBeanPHP providing the basic
* architectural building blocks to manager objects, helpers and additional tools
* like plugins. A toolbox contains the three core components of RedBeanPHP:
* the adapter, the query writer and the core functionality of RedBeanPHP in
* OODB.
*
* @file RedBeanPHP/ToolBox.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class ToolBox
{
/**
* @var OODB
*/
protected $oodb;
/**
* @var QueryWriter
*/
protected $writer;
/**
* @var DBAdapter
*/
protected $adapter;
/**
* Constructor.
* The toolbox is an integral part of RedBeanPHP providing the basic
* architectural building blocks to manager objects, helpers and additional tools
* like plugins. A toolbox contains the three core components of RedBeanPHP:
* the adapter, the query writer and the core functionality of RedBeanPHP in
* OODB.
*
* Usage:
*
* <code>
* $toolbox = new ToolBox( $oodb, $adapter, $writer );
* $plugin = new MyPlugin( $toolbox );
* </code>
*
* The example above illustrates how the toolbox is used.
* The core objects are passed to the ToolBox constructor to
* assemble a toolbox instance. The toolbox is then passed to
* the plugin, helper or manager object. Instances of
* TagManager, AssociationManager and so on are examples of
* this, they all require a toolbox. The toolbox can also
* be obtained from the facade using: R::getToolBox();
*
* @param OODB $oodb Object Database, OODB
* @param DBAdapter $adapter Database Adapter
* @param QueryWriter $writer Query Writer
*/
public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
{
$this->oodb = $oodb;
$this->adapter = $adapter;
$this->writer = $writer;
return $this;
}
/**
* Returns the query writer in this toolbox.
* The Query Writer is responsible for building the queries for a
* specific database and executing them through the adapter.
*
* Usage:
*
* <code>
* $toolbox = R::getToolBox();
* $redbean = $toolbox->getRedBean();
* $adapter = $toolbox->getDatabaseAdapter();
* $writer = $toolbox->getWriter();
* </code>
*
* The example above illustrates how to obtain the core objects
* from a toolbox instance. If you are working with the R-object
* only, the following shortcuts exist as well:
*
* - R::getRedBean()
* - R::getDatabaseAdapter()
* - R::getWriter()
*
* @return QueryWriter
*/
public function getWriter()
{
return $this->writer;
}
/**
* Returns the OODB instance in this toolbox.
* OODB is responsible for creating, storing, retrieving and deleting
* single beans. Other components rely
* on OODB for their basic functionality.
*
* Usage:
*
* <code>
* $toolbox = R::getToolBox();
* $redbean = $toolbox->getRedBean();
* $adapter = $toolbox->getDatabaseAdapter();
* $writer = $toolbox->getWriter();
* </code>
*
* The example above illustrates how to obtain the core objects
* from a toolbox instance. If you are working with the R-object
* only, the following shortcuts exist as well:
*
* - R::getRedBean()
* - R::getDatabaseAdapter()
* - R::getWriter()
*
* @return OODB
*/
public function getRedBean()
{
return $this->oodb;
}
/**
* Returns the database adapter in this toolbox.
* The adapter is responsible for executing the query and binding the values.
* The adapter also takes care of transaction handling.
*
* Usage:
*
* <code>
* $toolbox = R::getToolBox();
* $redbean = $toolbox->getRedBean();
* $adapter = $toolbox->getDatabaseAdapter();
* $writer = $toolbox->getWriter();
* </code>
*
* The example above illustrates how to obtain the core objects
* from a toolbox instance. If you are working with the R-object
* only, the following shortcuts exist as well:
*
* - R::getRedBean()
* - R::getDatabaseAdapter()
* - R::getWriter()
*
* @return DBAdapter
*/
public function getDatabaseAdapter()
{
return $this->adapter;
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException as RedException;
/**
* Array Tool Helper
*
* This code was originally part of the facade, however it has
* been decided to remove unique features to service classes like
* this to make them available to developers not using the facade class.
*
* This is a helper or service class containing frequently used
* array functions for dealing with SQL queries.
*
* @file RedBeanPHP/Util/ArrayTool.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class ArrayTool
{
/**
* Generates question mark slots for an array of values.
* Given an array and an optional template string this method
* will produce string containing parameter slots for use in
* an SQL query string.
*
* Usage:
*
* <code>
* R::genSlots( array( 'a', 'b' ) );
* </code>
*
* The statement in the example will produce the string:
* '?,?'.
*
* Another example, using a template string:
*
* <code>
* R::genSlots( array('a', 'b'), ' IN( %s ) ' );
* </code>
*
* The statement in the example will produce the string:
* ' IN( ?,? ) '.
*
* @param array $array array to generate question mark slots for
* @param string $template template to use
*
* @return string
*/
public static function genSlots( $array, $template = NULL )
{
$str = count( $array ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
return ( is_null( $template ) || $str === '' ) ? $str : sprintf( $template, $str );
}
/**
* Flattens a multi dimensional bindings array for use with genSlots().
*
* Usage:
*
* <code>
* R::flat( array( 'a', array( 'b' ), 'c' ) );
* </code>
*
* produces an array like: [ 'a', 'b', 'c' ]
*
* @param array $array array to flatten
* @param array $result result array parameter (for recursion)
*
* @return array
*/
public static function flat( $array, $result = array() )
{
foreach( $array as $value ) {
if ( is_array( $value ) ) $result = self::flat( $value, $result );
else $result[] = $value;
}
return $result;
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\Finder;
/**
* Diff Utility
*
* The Look Utility class provides an easy way to generate
* tables and selects (pulldowns) from the database.
*
* @file RedBeanPHP/Util/Diff.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Diff
{
/**
* @var Toolbox
*/
protected $toolbox;
/**
* Constructor.
* The MatchUp class requires a toolbox
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
}
/**
* Calculates a diff between two beans (or arrays of beans).
* The result of this method is an array describing the differences of the second bean compared to
* the first, where the first bean is taken as reference. The array is keyed by type/property, id and property name, where
* type/property is either the type (in case of the root bean) or the property of the parent bean where the type resides.
* The diffs are mainly intended for logging, you cannot apply these diffs as patches to other beans.
* However this functionality might be added in the future.
*
* The keys of the array can be formatted using the $format parameter.
* A key will be composed of a path (1st), id (2nd) and property (3rd).
* Using printf-style notation you can determine the exact format of the key.
* The default format will look like:
*
* 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
*
* If you only want a simple diff of one bean and you don't care about ids,
* you might pass a format like: '%1$s.%3$s' which gives:
*
* 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
*
* The filter parameter can be used to set filters, it should be an array
* of property names that have to be skipped. By default this array is filled with
* two strings: 'created' and 'modified'.
*
* @param OODBBean|array $beans reference beans
* @param OODBBean|array $others beans to compare
* @param array $filters names of properties of all beans to skip
* @param string $format the format of the key, defaults to '%s.%s.%s'
* @param string $type type/property of bean to use for key generation
*
* @return array
*/
public function diff( $beans, $others, $filters = array( 'created', 'modified' ), $format = '%s.%s.%s', $type = NULL )
{
$diff = array();
if ( !is_array( $beans ) ) $beans = array( $beans );
$beansI = array();
foreach ( $beans as $bean ) {
if ( !( $bean instanceof OODBBean ) ) continue;
$beansI[$bean->id] = $bean;
}
if ( !is_array( $others ) ) $others = array( $others );
$othersI = array();
foreach ( $others as $other ) {
if ( !( $other instanceof OODBBean ) ) continue;
$othersI[$other->id] = $other;
}
if ( count( $beansI ) == 0 || count( $othersI ) == 0 ) {
return array();
}
$type = $type != NULL ? $type : reset($beansI)->getMeta( 'type' );
foreach( $beansI as $id => $bean ) {
if ( !isset( $othersI[$id] ) ) continue;
$other = $othersI[$id];
foreach( $bean as $property => $value ) {
if ( in_array( $property, $filters ) ) continue;
$key = vsprintf( $format, array( $type, $bean->id, $property ) );
$compare = $other->{$property};
if ( !is_object( $value ) && !is_array( $value ) && $value != $compare ) {
$diff[$key] = array( $value, $compare );
} else {
$diff = array_merge( $diff, $this->diff( $value, $compare, $filters, $format, $key ) );
}
}
}
return $diff;
}
}

View File

@ -0,0 +1,209 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\RedException as RedException;
/**
* Dispense Helper
*
* A helper class containing a dispense utility.
*
* @file RedBeanPHP/Util/DispenseHelper.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class DispenseHelper
{
/**
* @var boolean
*/
private static $enforceNamingPolicy = TRUE;
/**
* Sets the enforce naming policy flag. If set to
* TRUE the RedBeanPHP naming policy will be enforced.
* Otherwise it will not. Use at your own risk.
* Setting this to FALSE is not recommended.
*
* @param boolean $yesNo whether to enforce RB name policy
*
* @return void
*/
public static function setEnforceNamingPolicy( $yesNo )
{
self::$enforceNamingPolicy = (boolean) $yesNo;
}
/**
* Checks whether the bean type conforms to the RedbeanPHP
* naming policy. This method will throw an exception if the
* type does not conform to the RedBeanPHP database column naming
* policy.
*
* The RedBeanPHP naming policy for beans states that valid
* bean type names contain only:
*
* - lowercase alphanumeric characters a-z
* - numbers 0-9
* - at least one character
*
* Although there are no restrictions on length, database
* specific implementations may apply further restrictions
* regarding the length of a table which means these restrictions
* also apply to bean types.
*
* The RedBeanPHP naming policy ensures that, without any
* configuration, the core functionalities work across many
* databases and operating systems, including those that are
* case insensitive or restricted to the ASCII character set.
*
* Although these restrictions can be bypassed, this is not
* recommended.
*
* @param string $type type of bean
*
* @return void
*/
public static function checkType( $type )
{
if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
throw new RedException( 'Invalid type: ' . $type );
}
}
/**
* Dispenses a new RedBean OODB Bean for use with
* the rest of the methods. RedBeanPHP thinks in beans, the bean is the
* primary way to interact with RedBeanPHP and the database managed by
* RedBeanPHP. To load, store and delete data from the database using RedBeanPHP
* you exchange these RedBeanPHP OODB Beans. The only exception to this rule
* are the raw query methods like R::getCell() or R::exec() and so on.
* The dispense method is the 'preferred way' to create a new bean.
*
* Usage:
*
* <code>
* $book = R::dispense( 'book' );
* $book->title = 'My Book';
* R::store( $book );
* </code>
*
* This method can also be used to create an entire bean graph at once.
* Given an array with keys specifying the property names of the beans
* and a special _type key to indicate the type of bean, one can
* make the Dispense Helper generate an entire hierarchy of beans, including
* lists. To make dispense() generate a list, simply add a key like:
* ownXList or sharedXList where X is the type of beans it contains and
* a set its value to an array filled with arrays representing the beans.
* Note that, although the type may have been hinted at in the list name,
* you still have to specify a _type key for every bean array in the list.
* Note that, if you specify an array to generate a bean graph, the number
* parameter will be ignored.
*
* Usage:
*
* <code>
* $book = R::dispense( [
* '_type' => 'book',
* 'title' => 'Gifted Programmers',
* 'author' => [ '_type' => 'author', 'name' => 'Xavier' ],
* 'ownPageList' => [ ['_type'=>'page', 'text' => '...'] ]
* ] );
* </code>
*
* @param string|array $typeOrBeanArray type or bean array to import
* @param integer $num number of beans to dispense
* @param boolean $alwaysReturnArray if TRUE always returns the result as an array
*
* @return array|OODBBean
*/
public static function dispense( OODB $oodb, $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) {
if ( is_array($typeOrBeanArray) ) {
if ( !isset( $typeOrBeanArray['_type'] ) ) {
$list = array();
foreach( $typeOrBeanArray as $beanArray ) {
if (
!( is_array( $beanArray )
&& isset( $beanArray['_type'] ) ) ) {
throw new RedException( 'Invalid Array Bean' );
}
}
foreach( $typeOrBeanArray as $beanArray ) $list[] = self::dispense( $oodb, $beanArray );
return $list;
}
$import = $typeOrBeanArray;
$type = $import['_type'];
unset( $import['_type'] );
} else {
$type = $typeOrBeanArray;
}
if (self::$enforceNamingPolicy) self::checkType( $type );
$beanOrBeans = $oodb->dispense( $type, $num, $alwaysReturnArray );
if ( isset( $import ) ) {
$beanOrBeans->import( $import );
}
return $beanOrBeans;
}
/**
* Takes a comma separated list of bean types
* and dispenses these beans. For each type in the list
* you can specify the number of beans to be dispensed.
*
* Usage:
*
* <code>
* list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
* </code>
*
* This will dispense a book, a page and a text. This way you can
* quickly dispense beans of various types in just one line of code.
*
* Usage:
*
* <code>
* list($book, $pages) = R::dispenseAll('book,page*100');
* </code>
*
* This returns an array with a book bean and then another array
* containing 100 page beans.
*
* @param OODB $oodb OODB
* @param string $order a description of the desired dispense order using the syntax above
* @param boolean $onlyArrays return only arrays even if amount < 2
*
* @return array
*/
public static function dispenseAll( OODB $oodb, $order, $onlyArrays = FALSE )
{
$list = array();
foreach( explode( ',', $order ) as $order ) {
if ( strpos( $order, '*' ) !== FALSE ) {
list( $type, $amount ) = explode( '*', $order );
} else {
$type = $order;
$amount = 1;
}
$list[] = self::dispense( $oodb, $type, $amount, $onlyArrays );
}
return $list;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Dump helper
*
* This code was originally part of the facade, however it has
* been decided to remove unique features to service classes like
* this to make them available to developers not using the facade class.
*
* Dumps the contents of a bean in an array for
* debugging purposes.
*
* @file RedBeanPHP/Util/Dump.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Dump
{
/**
* Dumps bean data to array.
* Given a one or more beans this method will
* return an array containing first part of the string
* representation of each item in the array.
*
* Usage:
*
* <code>
* echo R::dump( $bean );
* </code>
*
* The example shows how to echo the result of a simple
* dump. This will print the string representation of the
* specified bean to the screen, limiting the output per bean
* to 35 characters to improve readability. Nested beans will
* also be dumped.
*
* @param OODBBean|array $data either a bean or an array of beans
*
* @return array
*/
public static function dump( $data )
{
$array = array();
if ( $data instanceof OODBBean ) {
$str = strval( $data );
if (strlen($str) > 35) {
$beanStr = substr( $str, 0, 35 ).'... ';
} else {
$beanStr = $str;
}
return $beanStr;
}
if ( is_array( $data ) ) {
foreach( $data as $key => $item ) {
$array[$key] = self::dump( $item );
}
}
return $array;
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean;
/**
* Feature Utility
*
* The Feature Utility class provides an easy way to turn
* on or off features. This allows us to introduce new features
* without accidentally breaking backward compatibility.
*
* @file RedBeanPHP/Util/Feature.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Feature
{
/* Feature set constants */
const C_FEATURE_NOVICE_LATEST = 'novice/latest';
const C_FEATURE_LATEST = 'latest';
const C_FEATURE_NOVICE_5_5 = 'novice/5.5';
const C_FEATURE_5_5 = '5.5';
const C_FEATURE_NOVICE_5_4 = 'novice/5.4';
const C_FEATURE_5_4 = '5.4';
const C_FEATURE_NOVICE_5_3 = 'novice/5.3';
const C_FEATURE_5_3 = '5.3';
const C_FEATURE_ORIGINAL = 'original';
/**
* Selects the feature set you want as specified by
* the label.
*
* Available labels:
*
* novice/latest:
* - forbid R::nuke()
* - enable automatic relation resolver based on foreign keys
* - forbid R::store(All)( $bean, TRUE ) (Hybrid mode)
* - use IS-NULL conditions in findLike() etc
*
* latest:
* - allow R::nuke()
* - enable auto resolve
* - allow hybrid mode
* - use IS-NULL conditions in findLike() etc
*
* novice/X or X:
* - keep everything as it was in version X
*
* Usage:
*
* <code>
* R::useFeatureSet( 'novice/latest' );
* </code>
*
* @param string $label label
*
* @return void
*/
public static function feature( $label ) {
switch( $label ) {
case self::C_FEATURE_NOVICE_LATEST:
case self::C_FEATURE_NOVICE_5_4:
case self::C_FEATURE_NOVICE_5_5:
OODBBean::useFluidCount( FALSE );
R::noNuke( TRUE );
R::setAllowHybridMode( FALSE );
R::useISNULLConditions( TRUE );
break;
case self::C_FEATURE_LATEST:
case self::C_FEATURE_5_4:
case self::C_FEATURE_5_5:
OODBBean::useFluidCount( FALSE );
R::noNuke( FALSE );
R::setAllowHybridMode( TRUE );
R::useISNULLConditions( TRUE );
break;
case self::C_FEATURE_NOVICE_5_3:
OODBBean::useFluidCount( TRUE );
R::noNuke( TRUE );
R::setAllowHybridMode( FALSE );
R::useISNULLConditions( FALSE );
break;
case self::C_FEATURE_5_3:
OODBBean::useFluidCount( TRUE );
R::noNuke( FALSE );
R::setAllowHybridMode( FALSE );
R::useISNULLConditions( FALSE );
break;
case self::C_FEATURE_ORIGINAL:
OODBBean::useFluidCount( TRUE );
R::noNuke( FALSE );
R::setAllowHybridMode( FALSE );
R::useISNULLConditions( FALSE );
break;
default:
throw new \Exception("Unknown feature set label.");
break;
}
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\Finder;
/**
* Look Utility
*
* The Look Utility class provides an easy way to generate
* tables and selects (pulldowns) from the database.
*
* @file RedBeanPHP/Util/Look.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Look
{
/**
* @var Toolbox
*/
protected $toolbox;
/**
* Constructor.
* The MatchUp class requires a toolbox
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
}
/**
* Takes an full SQL query with optional bindings, a series of keys, a template
* and optionally a filter function and glue and assembles a view from all this.
* This is the fastest way from SQL to view. Typically this function is used to
* generate pulldown (select tag) menus with options queried from the database.
*
* Usage:
*
* <code>
* $htmlPulldown = R::look(
* 'SELECT * FROM color WHERE value != ? ORDER BY value ASC',
* [ 'g' ],
* [ 'value', 'name' ],
* '<option value="%s">%s</option>',
* 'strtoupper',
* "\n"
* );
*</code>
*
* The example above creates an HTML fragment like this:
*
* <option value="B">BLUE</option>
* <option value="R">RED</option>
*
* to pick a color from a palette. The HTML fragment gets constructed by
* an SQL query that selects all colors that do not have value 'g' - this
* excludes green. Next, the bean properties 'value' and 'name' are mapped to the
* HTML template string, note that the order here is important. The mapping and
* the HTML template string follow vsprintf-rules. All property values are then
* passed through the specified filter function 'strtoupper' which in this case
* is a native PHP function to convert strings to uppercase characters only.
* Finally the resulting HTML fragment strings are glued together using a
* newline character specified in the last parameter for readability.
*
* In previous versions of RedBeanPHP you had to use:
* R::getLook()->look() instead of R::look(). However to improve useability of the
* library the look() function can now directly be invoked from the facade.
*
* @param string $sql query to execute
* @param array $bindings parameters to bind to slots mentioned in query or an empty array
* @param array $keys names in result collection to map to template
* @param string $template HTML template to fill with values associated with keys, use printf notation (i.e. %s)
* @param callable $filter function to pass values through (for translation for instance)
* @param string $glue optional glue to use when joining resulting strings
*
* @return string
*/
public function look( $sql, $bindings = array(), $keys = array( 'selected', 'id', 'name' ), $template = '<option %s value="%s">%s</option>', $filter = 'trim', $glue = '' )
{
$adapter = $this->toolbox->getDatabaseAdapter();
$lines = array();
$rows = $adapter->get( $sql, $bindings );
foreach( $rows as $row ) {
$values = array();
foreach( $keys as $key ) {
if (!empty($filter)) {
$values[] = call_user_func_array( $filter, array( $row[$key] ) );
} else {
$values[] = $row[$key];
}
}
$lines[] = vsprintf( $template, $values );
}
$string = implode( $glue, $lines );
return $string;
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\Finder;
/**
* MatchUp Utility
*
* Tired of creating login systems and password-forget systems?
* MatchUp is an ORM-translation of these kind of problems.
* A matchUp is a match-and-update combination in terms of beans.
* Typically login related problems are all about a match and
* a conditional update.
*
* @file RedBeanPHP/Util/MatchUp.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class MatchUp
{
/**
* @var Toolbox
*/
protected $toolbox;
/**
* Constructor.
* The MatchUp class requires a toolbox
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
}
/**
* MatchUp is a powerful productivity boosting method that can replace simple control
* scripts with a single RedBeanPHP command. Typically, matchUp() is used to
* replace login scripts, token generation scripts and password reset scripts.
* The MatchUp method takes a bean type, an SQL query snippet (starting at the WHERE clause),
* SQL bindings, a pair of task arrays and a bean reference.
*
* If the first 3 parameters match a bean, the first task list will be considered,
* otherwise the second one will be considered. On consideration, each task list,
* an array of keys and values will be executed. Every key in the task list should
* correspond to a bean property while every value can either be an expression to
* be evaluated or a closure (PHP 5.3+). After applying the task list to the bean
* it will be stored. If no bean has been found, a new bean will be dispensed.
*
* This method will return TRUE if the bean was found and FALSE if not AND
* there was a NOT-FOUND task list. If no bean was found AND there was also
* no second task list, NULL will be returned.
*
* To obtain the bean, pass a variable as the sixth parameter.
* The function will put the matching bean in the specified variable.
*
* Usage (this example resets a password in one go):
*
* <code>
* $newpass = '1234';
* $didResetPass = R::matchUp(
* 'account', ' token = ? AND tokentime > ? ',
* [ $token, time()-100 ],
* [ 'pass' => $newpass, 'token' => '' ],
* NULL,
* $account );
* </code>
*
* @param string $type type of bean you're looking for
* @param string $sql SQL snippet (starting at the WHERE clause, omit WHERE-keyword)
* @param array $bindings array of parameter bindings for SQL snippet
* @param array $onFoundDo task list to be considered on finding the bean
* @param array $onNotFoundDo task list to be considered on NOT finding the bean
* @param OODBBean &$bean reference to obtain the found bean
*
* @return mixed
*/
public function matchUp( $type, $sql, $bindings = array(), $onFoundDo = NULL, $onNotFoundDo = NULL, &$bean = NULL )
{
$finder = new Finder( $this->toolbox );
$oodb = $this->toolbox->getRedBean();
$bean = $finder->findOne( $type, $sql, $bindings );
if ( $bean && $onFoundDo ) {
foreach( $onFoundDo as $property => $value ) {
if ( function_exists('is_callable') && is_callable( $value ) ) {
$bean[$property] = call_user_func_array( $value, array( $bean ) );
} else {
$bean[$property] = $value;
}
}
$oodb->store( $bean );
return TRUE;
}
if ( $onNotFoundDo ) {
$bean = $oodb->dispense( $type );
foreach( $onNotFoundDo as $property => $value ) {
if ( function_exists('is_callable') && is_callable( $value ) ) {
$bean[$property] = call_user_func_array( $value, array( $bean ) );
} else {
$bean[$property] = $value;
}
}
$oodb->store( $bean );
return FALSE;
}
return NULL;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
/**
* Multi Bean Loader Helper
*
* This code was originally part of the facade, however it has
* been decided to remove unique features to service classes like
* this to make them available to developers not using the facade class.
*
* This helper class offers limited support for one-to-one
* relations by providing a service to load a set of beans
* with differnt types and a common ID.
*
* @file RedBeanPHP/Util/MultiLoader.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class MultiLoader
{
/**
* Loads multiple types of beans with the same ID.
* This might look like a strange method, however it can be useful
* for loading a one-to-one relation. In a typical 1-1 relation,
* you have two records sharing the same primary key.
* RedBeanPHP has only limited support for 1-1 relations.
* In general it is recommended to use 1-N for this.
*
* Usage:
*
* <code>
* list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
* </code>
*
* @param OODB $oodb OODB object
* @param string|array $types the set of types to load at once
* @param mixed $id the common ID
*
* @return OODBBean
*/
public static function load( OODB $oodb, $types, $id )
{
if ( is_string( $types ) ) $types = explode( ',', $types );
if ( !is_array( $types ) ) return array();
foreach ( $types as $k => $typeItem ) {
$types[$k] = $oodb->load( $typeItem, $id );
}
return $types;
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\ToolBox as ToolBox;
/**
* Quick Export Utility
*
* The Quick Export Utility Class provides functionality to easily
* expose the result of SQL queries as well-known formats like CSV.
*
* @file RedBeanPHP/Util/QuickExporft.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class QuickExport
{
/**
* @var Finder
*/
protected $toolbox;
/**
* @boolean
*/
private static $test = FALSE;
/**
* Constructor.
* The Quick Export requires a toolbox.
*
* @param ToolBox $toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
}
/**
* Makes csv() testable.
*/
public static function operation( $name, $arg1, $arg2 = TRUE ) {
$out = '';
switch( $name ) {
case 'test':
self::$test = (boolean) $arg1;
break;
case 'header':
$out = ( self::$test ) ? $arg1 : header( $arg1, $arg2 );
break;
case 'readfile':
$out = ( self::$test ) ? file_get_contents( $arg1 ) : readfile( $arg1 );
break;
case 'exit':
$out = ( self::$test ) ? 'exit' : exit();
break;
}
return $out;
}
/**
* Exposes the result of the specified SQL query as a CSV file.
*
* Usage:
*
* <code>
* R::csv( 'SELECT
* `name`,
* population
* FROM city
* WHERE region = :region ',
* array( ':region' => 'Denmark' ),
* array( 'city', 'population' ),
* '/tmp/cities.csv'
* );
* </code>
*
* The command above will select all cities in Denmark
* and create a CSV with columns 'city' and 'population' and
* populate the cells under these column headers with the
* names of the cities and the population numbers respectively.
*
* @param string $sql SQL query to expose result of
* @param array $bindings parameter bindings
* @param array $columns column headers for CSV file
* @param string $path path to save CSV file to
* @param boolean $output TRUE to output CSV directly using readfile
* @param array $options delimiter, quote and escape character respectively
*
* @return void
*/
public function csv( $sql = '', $bindings = array(), $columns = NULL, $path = '/tmp/redexport_%s.csv', $output = TRUE, $options = array(',','"','\\') )
{
list( $delimiter, $enclosure, $escapeChar ) = $options;
$path = sprintf( $path, date('Ymd_his') );
$handle = fopen( $path, 'w' );
if ($columns) if (PHP_VERSION_ID>=505040) fputcsv($handle, $columns, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $columns, $delimiter, $enclosure );
$cursor = $this->toolbox->getDatabaseAdapter()->getCursor( $sql, $bindings );
while( $row = $cursor->getNextItem() ) {
if (PHP_VERSION_ID>=505040) fputcsv($handle, $row, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $row, $delimiter, $enclosure );
}
fclose($handle);
if ( $output ) {
$file = basename($path);
$out = self::operation('header',"Pragma: public");
$out .= self::operation('header',"Expires: 0");
$out .= self::operation('header',"Cache-Control: must-revalidate, post-check=0, pre-check=0");
$out .= self::operation('header',"Cache-Control: private", FALSE );
$out .= self::operation('header',"Content-Type: text/csv");
$out .= self::operation('header',"Content-Disposition: attachment; filename={$file}" );
$out .= self::operation('header',"Content-Transfer-Encoding: binary");
$out .= self::operation('readfile',$path );
@unlink( $path );
self::operation('exit', FALSE);
return $out;
}
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\Adapter as Adapter;
/**
* Transaction Helper
*
* This code was originally part of the facade, however it has
* been decided to remove unique features to service classes like
* this to make them available to developers not using the facade class.
*
* Database transaction helper. This is a convenience class
* to perform a callback in a database transaction. This class
* contains a method to wrap your callback in a transaction.
*
* @file RedBeanPHP/Util/Transaction.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Transaction
{
/**
* Wraps a transaction around a closure or string callback.
* If an Exception is thrown inside, the operation is automatically rolled back.
* If no Exception happens, it commits automatically.
* It also supports (simulated) nested transactions (that is useful when
* you have many methods that needs transactions but are unaware of
* each other).
*
* Example:
*
* <code>
* $from = 1;
* $to = 2;
* $amount = 300;
*
* R::transaction(function() use($from, $to, $amount)
* {
* $accountFrom = R::load('account', $from);
* $accountTo = R::load('account', $to);
* $accountFrom->money -= $amount;
* $accountTo->money += $amount;
* R::store($accountFrom);
* R::store($accountTo);
* });
* </code>
*
* @param Adapter $adapter Database Adapter providing transaction mechanisms.
* @param callable $callback Closure (or other callable) with the transaction logic
*
* @return mixed
*/
public static function transaction( Adapter $adapter, $callback )
{
if ( !is_callable( $callback ) ) {
throw new RedException( 'R::transaction needs a valid callback.' );
}
static $depth = 0;
$result = null;
try {
if ( $depth == 0 ) {
$adapter->startTransaction();
}
$depth++;
$result = call_user_func( $callback ); //maintain 5.2 compatibility
$depth--;
if ( $depth == 0 ) {
$adapter->commit();
}
} catch ( \Exception $exception ) {
$depth--;
if ( $depth == 0 ) {
$adapter->rollback();
}
throw $exception;
}
return $result;
}
}

View File

@ -0,0 +1,212 @@
<?php
namespace RedBeanPHP\Util;
use RedBeanPHP\ToolBox;
use RedBeanPHP\OODBBean;
/**
* Tree
*
* Given a bean, finds it children or parents
* in a hierchical structure.
*
* @experimental feature
*
* @file RedBeanPHP/Util/Tree.php
* @author Gabor de Mooij and the RedBeanPHP Community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Tree {
/**
* @var ToolBox
*/
protected $toolbox;
/**
* @var QueryWriter
*/
protected $writer;
/**
* @var OODB
*/
protected $oodb;
/**
* Constructor, creates a new instance of
* the Tree.
*
* @param ToolBox $toolbox toolbox
*/
public function __construct( ToolBox $toolbox )
{
$this->toolbox = $toolbox;
$this->writer = $toolbox->getWriter();
$this->oodb = $toolbox->getRedBean();
}
/**
* Returns all child beans associates with the specified
* bean in a tree structure.
*
* @note this only works for databases that support
* recusrive common table expressions.
*
* Usage:
*
* <code>
* $newsArticles = R::children( $newsPage, ' ORDER BY title ASC ' )
* $newsArticles = R::children( $newsPage, ' WHERE title = ? ', [ $t ] );
* $newsArticles = R::children( $newsPage, ' WHERE title = :t ', [ ':t' => $t ] );
* </code>
*
* Note:
* You are allowed to use named parameter bindings as well as
* numeric parameter bindings (using the question mark notation).
* However, you can not mix. Also, if using named parameter bindings,
* parameter binding key ':slot0' is reserved for the ID of the bean
* and used in the query.
*
* @param OODBBean $bean reference bean to find children of
* @param string $sql optional SQL snippet
* @param array $bindings optional parameter bindings for SQL snippet
*
* @return array
*/
public function children( OODBBean $bean, $sql = NULL, $bindings = array() )
{
$type = $bean->getMeta('type');
$id = $bean->id;
$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, FALSE, $sql, $bindings );
return $this->oodb->convertToBeans( $type, $rows );
}
/**
* Returns all parent beans associates with the specified
* bean in a tree structure.
*
* @note this only works for databases that support
* recusrive common table expressions.
*
* <code>
* $newsPages = R::parents( $newsArticle, ' ORDER BY title ASC ' );
* $newsPages = R::parents( $newsArticle, ' WHERE title = ? ', [ $t ] );
* $newsPages = R::parents( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
* </code>
*
* Note:
* You are allowed to use named parameter bindings as well as
* numeric parameter bindings (using the question mark notation).
* However, you can not mix. Also, if using named parameter bindings,
* parameter binding key ':slot0' is reserved for the ID of the bean
* and used in the query.
*
* @param OODBBean $bean reference bean to find parents of
* @param string $sql optional SQL snippet
* @param array $bindings optional parameter bindings for SQL snippet
*
* @return array
*/
public function parents( OODBBean $bean, $sql = NULL, $bindings = array() )
{
$type = $bean->getMeta('type');
$id = $bean->id;
$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, TRUE, $sql, $bindings );
return $this->oodb->convertToBeans( $type, $rows );
}
/**
* Counts all children beans associates with the specified
* bean in a tree structure.
*
* @note this only works for databases that support
* recusrive common table expressions.
*
* <code>
* $count = R::countChildren( $newsArticle );
* $count = R::countChildren( $newsArticle, ' WHERE title = ? ', [ $t ] );
* $count = R::countChildren( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
* </code>
*
* @note:
* You are allowed to use named parameter bindings as well as
* numeric parameter bindings (using the question mark notation).
* However, you can not mix. Also, if using named parameter bindings,
* parameter binding key ':slot0' is reserved for the ID of the bean
* and used in the query.
*
* @note:
* By default, if no SQL or select is given or select=TRUE this method will subtract 1 of
* the total count to omit the starting bean. If you provide your own select,
* this method assumes you take control of the resulting total yourself since
* it cannot 'predict' what or how you are trying to 'count'.
*
* @param OODBBean $bean reference bean to find children of
* @param string $sql optional SQL snippet
* @param array $bindings optional parameter bindings for SQL snippet
* @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
*
* @return integer
*/
public function countChildren( OODBBean $bean, $sql = NULL, $bindings = array(), $select = TRUE ) {
$type = $bean->getMeta('type');
$id = $bean->id;
$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, FALSE, $sql, $bindings, $select );
$first = reset($rows);
$cell = reset($first);
return (intval($cell) - (($select === TRUE && is_null($sql)) ? 1 : 0));
}
/**
* Counts all parent beans associates with the specified
* bean in a tree structure.
*
* @note this only works for databases that support
* recusrive common table expressions.
*
* <code>
* $count = R::countParents( $newsArticle );
* $count = R::countParents( $newsArticle, ' WHERE title = ? ', [ $t ] );
* $count = R::countParents( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
* </code>
*
* Note:
* You are allowed to use named parameter bindings as well as
* numeric parameter bindings (using the question mark notation).
* However, you can not mix. Also, if using named parameter bindings,
* parameter binding key ':slot0' is reserved for the ID of the bean
* and used in the query.
*
* Note:
* By default, if no SQL or select is given or select=TRUE this method will subtract 1 of
* the total count to omit the starting bean. If you provide your own select,
* this method assumes you take control of the resulting total yourself since
* it cannot 'predict' what or how you are trying to 'count'.
*
* @param OODBBean $bean reference bean to find parents of
* @param string $sql optional SQL snippet
* @param array $bindings optional parameter bindings for SQL snippet
* @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
*
* @return integer
*/
public function countParents( OODBBean $bean, $sql = NULL, $bindings = array(), $select = TRUE ) {
$type = $bean->getMeta('type');
$id = $bean->id;
$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, TRUE, $sql, $bindings, $select );
$first = reset($rows);
$cell = reset($first);
return (intval($cell) - (($select === TRUE && is_null($sql)) ? 1 : 0));
}
}

View File

@ -0,0 +1,316 @@
RedBeanPHP
Written by Gabor de Mooij
RedBean is DUAL Licensed New BSD and GPLv2. You may choose the license that fits
best for your project.
New BSD License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of RedBeanPHP nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY GABOR DE MOOIJ ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL GABOR DE MOOIJ BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
RedBeanPHP is Written by Gabor de Mooij (G.J.G.T de Mooij) Copyright (c) 2018.
GPLv2 LICENSE
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

View File

@ -0,0 +1,85 @@
<?php
//Set the directory path
define('REDBEANPHP_MAIN_DIR', 'phar://rb.phar/RedBeanPHP/');
//Load Database drivers
require( REDBEANPHP_MAIN_DIR . 'Logger.php' );
require( REDBEANPHP_MAIN_DIR . 'Logger/RDefault.php' );
require( REDBEANPHP_MAIN_DIR . 'Logger/RDefault/Debug.php' );
require( REDBEANPHP_MAIN_DIR . 'Driver.php' );
require( REDBEANPHP_MAIN_DIR . 'Driver/RPDO.php' );
//Load Infrastructure
require( REDBEANPHP_MAIN_DIR . 'OODBBean.php' );
require( REDBEANPHP_MAIN_DIR . 'Observable.php' );
require( REDBEANPHP_MAIN_DIR . 'Observer.php' );
//Load Database Adapters
require( REDBEANPHP_MAIN_DIR . 'Adapter.php' );
require( REDBEANPHP_MAIN_DIR . 'Adapter/DBAdapter.php' );
require( REDBEANPHP_MAIN_DIR . 'Cursor.php');
require( REDBEANPHP_MAIN_DIR . 'Cursor/PDOCursor.php');
require( REDBEANPHP_MAIN_DIR . 'Cursor/NullCursor.php');
require( REDBEANPHP_MAIN_DIR . 'BeanCollection.php' );
//Load SQL drivers
require( REDBEANPHP_MAIN_DIR . 'QueryWriter.php' );
require( REDBEANPHP_MAIN_DIR . 'QueryWriter/AQueryWriter.php' );
require( REDBEANPHP_MAIN_DIR . 'QueryWriter/MySQL.php' );
require( REDBEANPHP_MAIN_DIR . 'QueryWriter/SQLiteT.php' );
require( REDBEANPHP_MAIN_DIR . 'QueryWriter/PostgreSQL.php' );
//Load required Exceptions
require( REDBEANPHP_MAIN_DIR . 'RedException.php' );
require( REDBEANPHP_MAIN_DIR . 'RedException/SQL.php' );
//Load Repository Classes
require( REDBEANPHP_MAIN_DIR . 'Repository.php' );
require( REDBEANPHP_MAIN_DIR . 'Repository/Fluid.php' );
require( REDBEANPHP_MAIN_DIR . 'Repository/Frozen.php' );
//Load Core functionality
require( REDBEANPHP_MAIN_DIR . 'OODB.php' );
require( REDBEANPHP_MAIN_DIR . 'ToolBox.php' );
require( REDBEANPHP_MAIN_DIR . 'Finder.php' );
//Load extended functionality
require( REDBEANPHP_MAIN_DIR . 'AssociationManager.php' );
require( REDBEANPHP_MAIN_DIR . 'BeanHelper.php' );
require( REDBEANPHP_MAIN_DIR . 'BeanHelper/SimpleFacadeBeanHelper.php' );
/* Developer Comfort */
require( REDBEANPHP_MAIN_DIR . 'SimpleModel.php' );
require( REDBEANPHP_MAIN_DIR . 'SimpleModelHelper.php' );
require( REDBEANPHP_MAIN_DIR . 'TagManager.php' );
require( REDBEANPHP_MAIN_DIR . 'LabelMaker.php' );
require( REDBEANPHP_MAIN_DIR . 'Facade.php' );
require( REDBEANPHP_MAIN_DIR . 'DuplicationManager.php' );
require( REDBEANPHP_MAIN_DIR . 'Plugin.php' );
require( REDBEANPHP_MAIN_DIR . 'Functions.php' );
/* Facade Utilities */
require( REDBEANPHP_MAIN_DIR . 'Util/ArrayTool.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/DispenseHelper.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Dump.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/MultiLoader.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Transaction.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/QuickExport.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/MatchUp.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Look.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Diff.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Tree.php' );
require( REDBEANPHP_MAIN_DIR . 'Util/Feature.php' );
//Allow users to mount the plugin folder.
if ( defined( 'REDBEANPHP_PLUGINS' ) ) {
Phar::mount( 'RedBeanPHP/Plugin', REDBEANPHP_PLUGINS );
}
//make some classes available for backward compatibility
class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
if (!class_exists('R')) {
class R extends \RedBeanPHP\Facade{};
}

View File

@ -0,0 +1,22 @@
{
"name": "gabordemooij/redbean",
"description": "RedBeanPHP ORM",
"keywords": ["orm"],
"homepage": "https://redbeanphp.com/",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Gabor de Mooij",
"email": "gabor@redbeanphp.com",
"homepage": "https://redbeanphp.com"
}
],
"require": {
"php": ">=5.3.4"
},
"autoload": {
"psr-4": {
"RedBeanPHP\\" : "RedBeanPHP"
}
}
}

View File

@ -0,0 +1,19 @@
<?php
echo "Running Patch P533...";
echo PHP_EOL;
$code = file_get_contents('rb.php');
$code = str_replace('&offsetGet', 'offsetGet', $code);
$bytes = file_put_contents('rb-p533.php', $code);
if ($bytes > 0) {
echo 'Applied patch for PHP < 5.3.3';
echo PHP_EOL;
exit;
} else {
echo 'Somthing went wrong.';
echo PHP_EOL;
exit;
}

View File

@ -0,0 +1,34 @@
<?php
echo "Welcome to Replica 2 Build Script for RedBeanPHP\n";
echo "Now building your beans!\n";
echo "-------------------------------------------\n";
echo "Cleaning up... ";
@exec('del /q rb.phar');
@exec('del /q build');
echo "Done.\n";
echo "Trying to create a directory called build to build the PHAR... ";
@mkdir('build');
@mkdir('build\RedBeanPHP');
echo "Done.\n";
echo "Trying to copy RedBeanPHP to build/RedBeanPHP... ";
@exec('xcopy .\RedBeanPHP .\build\RedBeanPHP /e /y');
echo "Done.\n";
echo "Moving loader to build folder... ";
@exec('move .\build\RedBeanPHP\loader.php .\build\loader.php');
echo "Done.\n";
echo "Creating PHAR archive... ";
$phar = new Phar("rb.phar", 0, "rb.phar");
$phar->buildFromDirectory('./build');
echo "Done.\n";
echo "Adding stub... ";
$phar->setStub($phar->createDefaultStub("loader.php"));
echo "Done.\n";
echo "Your PHAR file has been generated.\n";

121
vendor/gabordemooij/redbean/replica2.php vendored Normal file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env php
<?php
echo "Welcome to Replica 2 Build Script for RedBeanPHP\n";
echo "Now building your beans!\n";
echo "-------------------------------------------\n";
$code = '';
$codeMySQL = '';
$codePostgres = '';
$codeSQLite = '';
function addFile($file, $only = null) {
global $code;
global $codeMySQL;
global $codePostgres;
global $codeSQLite;
echo 'Added ', $file , ' to package... ',PHP_EOL;
$raw = file_get_contents($file);
$raw = preg_replace('/namespace\s+([a-zA-Z0-9\\\;]+);/m', 'namespace $1 {', $raw);
$raw .= '}';
$code .= $raw;
if ( $only == null || $only == 'mysql' ) $codeMySQL .= $raw;
if ( $only == null || $only == 'postgres' ) $codePostgres .= $raw;
if ( $only == null || $only == 'sqlite' ) $codeSQLite .= $raw;
}
define('DIR', 'RedBeanPHP/');
addFile( DIR . 'Logger.php' );
addFile( DIR . 'Logger/RDefault.php' );
addFile( DIR . 'Logger/RDefault/Debug.php' );
addFile( DIR . 'Driver.php' );
addFile( DIR . 'Driver/RPDO.php' );
addFile( DIR . 'OODBBean.php' );
addFile( DIR . 'Observable.php' );
addFile( DIR . 'Observer.php' );
addFile( DIR . 'Adapter.php' );
addFile( DIR . 'Adapter/DBAdapter.php' );
addFile( DIR . 'Cursor.php');
addFile( DIR . 'Cursor/PDOCursor.php');
addFile( DIR . 'Cursor/NullCursor.php');
addFile( DIR . 'BeanCollection.php' );
addFile( DIR . 'QueryWriter.php' );
addFile( DIR . 'QueryWriter/AQueryWriter.php' );
addFile( DIR . 'QueryWriter/MySQL.php', 'mysql' );
addFile( DIR . 'QueryWriter/SQLiteT.php', 'sqlite' );
addFile( DIR . 'QueryWriter/PostgreSQL.php', 'postgres' );
addFile( DIR . 'QueryWriter/CUBRID.php' );
addFile( DIR . 'RedException.php' );
addFile( DIR . 'RedException/SQL.php' );
addFile( DIR . 'Repository.php' );
addFile( DIR . 'Repository/Fluid.php' );
addFile( DIR . 'Repository/Frozen.php' );
addFile( DIR . 'OODB.php' );
addFile( DIR . 'ToolBox.php' );
addFile( DIR . 'Finder.php' );
addFile( DIR . 'AssociationManager.php' );
addFile( DIR . 'BeanHelper.php' );
addFile( DIR . 'BeanHelper/SimpleFacadeBeanHelper.php' );
addFile( DIR . 'SimpleModel.php' );
addFile( DIR . 'SimpleModelHelper.php' );
addFile( DIR . 'TagManager.php' );
addFile( DIR . 'LabelMaker.php' );
addFile( DIR . 'Facade.php' );
addFile( DIR . 'DuplicationManager.php' );
addFile( DIR . 'Util/ArrayTool.php' );
addFile( DIR . 'Util/DispenseHelper.php' );
addFile( DIR . 'Util/Dump.php' );
addFile( DIR . 'Util/MultiLoader.php' );
addFile( DIR . 'Util/Transaction.php' );
addFile( DIR . 'Util/QuickExport.php' );
addFile( DIR . 'Util/MatchUp.php' );
addFile( DIR . 'Util/Look.php' );
addFile( DIR . 'Util/Diff.php' );
addFile( DIR . 'Util/Tree.php' );
addFile( DIR . 'Util/Feature.php' );
addFile( DIR . 'Plugin.php' );
$func = file_get_contents(DIR . 'Functions.php');
$extra = "
namespace {
//make some classes available for backward compatibility
class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
if (!class_exists('R')) {
class R extends \RedBeanPHP\Facade{};
}
$func
}
";
$code .= $extra;
$codeMySQL .= $extra;
$codePostgres .= $extra;
$codeSQLite .= $extra;
$code = '<?php'.str_replace('<?php', '', $code);
$codeMySQL = '<?php'.str_replace('<?php', '', $codeMySQL);
$codePostgres = '<?php'.str_replace('<?php', '', $codePostgres);
$codeSQLite = '<?php'.str_replace('<?php', '', $codeSQLite);
$files = array( 'rb.php' => $code, 'rb-mysql.php' => $codeMySQL, 'rb-postgres.php' => $codePostgres, 'rb-sqlite.php' => $codeSQLite );
foreach( $files as $file => $content ) {
echo 'Okay, seems we have all the code.. now writing file: ', $file ,PHP_EOL;
$b = file_put_contents($file, $content);
echo 'Written: ',$b,' bytes.',PHP_EOL;
if ($b > 0) {
echo 'Done!' ,PHP_EOL;
} else {
echo 'Hm, something seems to have gone wrong... ',PHP_EOL;
}
}

View File

@ -0,0 +1,6 @@
#!/bin/sh
php replica2.php
cp rb.php testing/cli/testcontainer/rb.php
cd testing
cd cli
php runtests.php

View File

@ -0,0 +1,11 @@
#!/bin/sh
if [ -z "$1" ]
then
echo "Please enter the name of a test suite, example: Blackhole/Version"
exit
fi
php replica2.php
cp rb.php testing/cli/testcontainer/rb.php
cd testing
cd cli
php runtests.php $1

View File

@ -0,0 +1,17 @@
; Test suite database config
; Rename this file to test.ini if you are done
[mysql]
host = "localhost"
schema = "oodb"
user = "root"
pass = "password"
[pgsql]
host = "localhost"
schema = "oodb"
user = "postgres"
pass = "password"
[sqlite]
file = "/tmp/database.db"

View File

@ -0,0 +1,162 @@
<?php
namespace RedUNIT;
use RedBeanPHP\Facade as R;
/**
* RedUNIT
* Base class for RedUNIT, the micro unit test suite for RedBeanPHP
*
* @file RedUNIT/RedUNIT.php
* @description Provides the basic logic for any unit test in RedUNIT.
* @author Gabor de Mooij
* @license BSD
*
*
* (c) G.J.G.T. (Gabor) de Mooij
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
abstract class RedUNIT
{
/**
* @var integer
*/
protected $round;
/**
* @var string
*/
protected $currentlyActiveDriverID = 'unknown';
/**
* What drivers should be loaded for this test pack ?
* This method should be implemented by the test to tell the
* test controller system what drivers are supposed to be tested.
* Each driver will be fed to the test in a 'round' (explained below).
*
* @return array
*/
abstract public function getTargetDrivers();
/**
* Prepare test pack (mostly: nuke the entire database).
* This method prepares the test for a run.
*/
public function prepare()
{
R::getDatabaseAdapter()->getDatabase()->connect();
try {
R::freeze( FALSE );
R::debug( FALSE );
R::nuke();
} catch( \Exception $e ) {
R::freeze( FALSE );
R::debug( FALSE );
R::nuke();
}
}
/**
* Runs the test. This method will run the tests implemented
* by the RedUNIT instance. The run method scans its class for
* all public instance methods except:
* run (to avoid recursion), getTargetDrivers, onEvent
* and prepare. -- added cleanUp/prepare just in case they get overridden.
*
* @return void
*/
public function run()
{
$old_error_handler = set_error_handler('redunit_error_handler');
$class = new \ReflectionClass( $this );
$skip = array( 'run', 'getTargetDrivers', 'onEvent', 'cleanUp', 'prepare' );
// Call all methods except run automatically
foreach ( $class->getMethods( \ReflectionMethod::IS_PUBLIC ) as $method ) {
// Skip methods inherited from parent class
if ( $method->class != $class->getName() ) continue;
if ( in_array( $method->name, $skip ) ) continue;
$classname = str_replace( $class->getParentClass()->getName().'_', '', $method->class );
printtext( "\n\t" . $classname."->".$method->name." [".$method->class."->".$method->name."]" . " \n\t" );
$call = $method->name;
$this->$call();
try {
R::nuke();
} catch( \PDOException $e ) {
// Some tests use a broken database on purpose, so an exception is ok
}
}
restore_error_handler();
}
/**
* Clean-up method, to be invoked after running the test.
* This is an empty implementation, it does nothing. However this method
* should be overridden by tests that require clean-up.
*
* @return void
*/
public function cleanUp()
{
}
/**
* Sets the current driver.
* This method is called by a test controller, runner or manager
* to activate the mode associated with the specified driver
* identifier. This mechanism allows a test to run slightly different
* in the context of another driver, for instance SQLite might not need
* some tests, or MySQL might need some additional tests etc...
*
* @param string $driver the driver identifier
*
* @return void
*/
public function setCurrentDriver( $driver )
{
$this->currentlyActiveDriverID = $driver;
}
/**
* Sets the round number.
* Each test can have a varying number of flavors.
* A test flavor is 'that same test' but for a different driver.
* Each 'run on a specific driver' is called a round.
* Some tests might want to know what the current round is.
* This method can be used to set the current round number.
*
* @param integer $roundNumber round, the current round number
*
* @return void
*/
public function setRound( $roundNumber )
{
$this->round = (int) $roundNumber;
}
/**
* Returns the current round number.
* The current round number indicates how many times
* this test has been applied (to various drivers).
*
* @return integer
*/
public function getRound()
{
return $this->round;
}
/**
* Detects whether the current round is the first one.
* If the current round is indeed the first round, this method
* will return boolean TRUE, otherwise it will return FALSE. Note that
* the rounds are 0-based, so - if the current round equals 0, this
* method will return TRUE.
*
* @return boolean
*/
public function isFirstRound()
{
return ( $this->round === 0 );
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace RedUNIT;
/**
* Base
*
* This is the base class for all multi-driver Unit Tests.
* By default base class derived tests will offer 'test rounds' for
* all well known RedBeanPHP drivers: mysql (MySQL/MariaDB), pgsql (PostgreSQL),
* sqlite (SQLite3) and CUBRID (CUBRID).
*
* @file RedUNIT/Base.php
* @desc Base class for all drivers that support all database systems.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Base extends RedUNIT
{
/**
* List of DB drivers.
* Contains the list of database drivers as returned by getTargetDrivers().
*
* @var array
*/
protected static $driverList = array( 'mysql', 'pgsql', 'sqlite', 'CUBRID' );
/**
* Adds a driver to the list.
*
* @param string $driverID driver identifier.
*/
public static function addToDriverList( $driverID )
{
self::$driverList[] = $driverID;
}
/**
* Returns a list of drivers for which this driver supports
* 'test rounds'. This class only supports all base drivers.
*
* @see RedUNIT::getTargetDrivers() for details.
*
* @return array
*/
public function getTargetDrivers()
{
return self::$driverList;
}
}

View File

@ -0,0 +1,387 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Aliasing
*
* Tests aliasing functionality, i.e. fetching beans as,
* inferring correct type and retrieving lists as alias.
*
* @file RedUNIT/Base/Aliasing.php
* @desc Tests for nested beans with aliases, i.e. teacher alias for person etc.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Aliasing extends Base
{
/**
* Test for aliasing issue for LTS version.
*
* @return void
*/
public function testIssueAliasingForLTSVersion() {
$person = R::dispense('person');
$pro = R::dispense('project');
$c = R::dispense('course');
$person->name = 'x';
$person->alias('teacher')->ownProject[] = $pro;
$person->alias('student')->ownCourse[] = $c;
R::store($person);
asrt($c->fresh()->fetchAs('person')->student->name, 'x');
asrt($pro->fresh()->fetchAs('person')->teacher->name, 'x');
$person = $person->fresh();
$person->alias('teacher')->ownProject = array();
$person->alias('student')->ownCourse = array();
R::store($person);
asrt($c->fresh()->fetchAs('person')->student, NULL);
asrt($pro->fresh()->fetchAs('person')->teacher, NULL);
}
/**
* Describing how clearing state of bean works.
* Every method returning somthing (except getID)
* clears prefix-method-state (anything set by withCond,with,alias,fetchAs).
*
* @return void
*/
public function clearStateAdditionalTests()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$ilse->name = 'Ilse';
$irene->name = 'Irene';
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$ilse = R::load( 'person', $ilse->id );
asrt( count( $ilse->alias( 'developer' )->ownProject ), 2);
//cached - same list
asrt( count( $ilse->ownProject ), 2);
asrt( count( $ilse->alias( 'designer' )->ownProject ), 0);
//cached - same list
asrt( count( $ilse->ownProject ), 0);
//now test state
asrt( count( $ilse->setAttr( 'a', 'b' )->alias( 'developer' )->ownProject ), 2);
//now test state
$ilse = $ilse->fresh();
//attr clears state...
asrt( count( $ilse->alias( 'developer' )->setAttr( 'a', 'b' )->ownProject ), 0);
//but getID() does not!
$ilse = $ilse->fresh();
$ilse->alias('developer');
$ilse->getID();
asrt( count( $ilse->ownProject ), 2 );
}
/**
* Can switch fetchAs().
* Also checks shadow by storing.
*
* @return void
*/
public function canSwitchParentBean()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$ilse->name = 'Ilse';
$irene->name = 'Irene';
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$project1 = R::load( 'project', $project1->id );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
R::store( $project1 );
$project1 = R::load( 'project', $project1->id );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
R::store( $project1 );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
asrt( $project1->fetchAs('person')->designer->name, 'Irene' );
asrt( $project1->fetchAs('person')->developer->name, 'Ilse' );
}
/**
* Switching aliases (->alias) should not change other list during
* storage.
*
* @return void
*/
public function testShadow()
{
list( $project1, $project2 ) = R::dispense( 'project', 2 );
list( $irene, $ilse ) = R::dispense('person', 2);
$project1->developer = $ilse;
$project1->designer = $irene;
$project2->developer = $ilse;
R::storeAll( array( $project1, $project2 ) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
R::store( $ilse );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
R::storeAll( array( $ilse, $irene) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $irene->alias('designer')->ownProject), 1 );
asrt( count( $irene->alias('developer')->ownProject), 0 );
R::storeAll( array( $ilse, $irene) );
$ilse = R::load( 'person', $ilse->id );
$irene = R::load( 'person', $irene->id );
asrt( count( $ilse->alias('designer')->ownProject ), 0 );
asrt( count( $ilse->alias('developer')->ownProject ), 2 );
asrt( count( $irene->alias('designer')->ownProject), 1 );
asrt( count( $irene->alias('developer')->ownProject), 0 );
}
/**
* Issue 291. State not cleared.
*
* @return void
*/
public function testFetchTypeConfusionIssue291()
{
list( $teacher, $student ) = R::dispense( 'person', 2 ) ;
$teacher->name = 'jimmy' ;
$student->name = 'jacko' ;
R::store( $teacher ) ;
R::store( $student ) ;
$client = R::dispense( 'client' ) ;
$client->firm = 'bean AG' ;
R::store( $client ) ;
$project = R::dispense( 'project' ) ;
$project->teacher = $teacher ;
$project->student = $student ;
$project->client = $client ;
R::store( $project ) ;
unset( $project->student ) ;
R::store( $project ) ;
$project = R::load( 'project', 1 ) ;
$teacher = $project->fetchAs( 'person' )->teacher ;
$student = $project->fetchAs( 'person' )->student ;
$client = $project->client ; // this will select from "person" instead of "client"
asrt( $client->firm, 'bean AG' );
}
/**
* Test switching alias (also issue #291).
*
* @return void
*/
public function testAliasSwitch()
{
$student = R::dispense( 'person' );
$project = R::dispense( 'project' );
$project->student = $student;
R::store( $project );
$person = R::load( 'person', $student->id );
asrt( count( $person->alias( 'student' )->ownProject ), 1);
asrt( count( $person->alias( 'teacher' )->ownProject ), 0);
}
/**
* Associating two beans, then loading the associated bean
*
* @return void
*/
public function testAssociated()
{
$person = R::dispense( 'person' );
$person->name = 'John';
R::store( $person );
$course = R::dispense( 'course' );
$course->name = 'Math';
R::store( $course );
$course->teacher = $person;
$id = R::store( $course );
$course = R::load( 'course', $id );
$teacher = $course->fetchAs( 'person' )->teacher;
asrt( $teacher->name, 'John' );
//Trying to load a property that has an invalid name
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->wrongProperty = array( $page );
try {
$book->wrongProperty[] = $page;
R::store( $book );
fail();
} catch ( RedException $e ) {
pass();
} catch ( \Exception $e ) {
fail();
}
}
/**
* Test for quick detect change.
*
* @return void
*/
public function basic()
{
$book = R::dispense( 'book' );
asrt( isset( $book->prop ), FALSE ); //not a very good test
asrt( in_array( 'prop', array_keys( $book->export() ) ), FALSE ); //better...
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->paper = $page;
$id = R::store( $book );
$book = R::load( 'book', $id );
asrt( FALSE, ( isset( $book->paper ) ) );
asrt( FALSE, ( isset( $book->page ) ) );
/**
* The following tests try to store various things that aren't
* beans (which is expected) with the own* and shared* properties
* which only accept beans as assignments, so they're expected to fail
*/
foreach ( array( 'a string', 1928, TRUE, NULL, array()) as $value ) {
try {
$book->ownPage[] = $value;
R::store( $book );
$book->sharedPage[] = $value;
R::store( $book );
fail();
} catch ( RedException $e ) {
pass();
} catch ( \Exception $e ) {
if (strpos($e->getMessage(),'Array to string conversion')===FALSE) {
fail();
}
}
}
}
/**
* Finding $person beans that have been aliased into various roles
*
* @return void
*/
public function testAliasedFinder()
{
$message = R::dispense( 'message' );
$message->subject = 'Roommate agreement';
list( $sender, $recipient ) = R::dispense( 'person', 2 );
$sender->name = 'Sheldon';
$recipient->name = 'Leonard';
$message->sender = $sender;
$message->recipient = $recipient;
$id = R::store( $message );
$message = R::load( 'message', $id );
asrt( $message->fetchAs( 'person' )->sender->name, 'Sheldon' );
asrt( $message->fetchAs( 'person' )->recipient->name, 'Leonard' );
$otherRecipient = R::dispense( 'person' );
$otherRecipient->name = 'Penny';
$message->recipient = $otherRecipient;
R::store( $message );
$message = R::load( 'message', $id );
asrt( $message->fetchAs( 'person' )->sender->name, 'Sheldon' );
asrt( $message->fetchAs( 'person' )->recipient->name, 'Penny' );
}
/**
* Test Basic Fetch AS functionality.
*/
public function testBasicFetchAs()
{
$project = R::dispense( 'project' );
$project->name = 'Mutant Project';
list( $teacher, $student ) = R::dispense( 'person', 2 );
$teacher->name = 'Charles Xavier';
$project->student = $student;
$project->student->name = 'Wolverine';
$project->teacher = $teacher;
$id = R::store( $project );
$project = R::load( 'project', $id );
asrt( $project->fetchAs( 'person' )->teacher->name, 'Charles Xavier' );
asrt( $project->fetchAs( 'person' )->student->name, 'Wolverine' );
}
/**
* Test Basic list variations.
*
* @return void
*/
public function testBasicListVariations()
{
$farm = R::dispense( 'building' );
$village = R::dispense( 'village' );
$farm->name = 'farm';
$village->name = 'Dusty Mountains';
$farm->village = $village;
$id = R::store( $farm );
$farm = R::load( 'building', $id );
asrt( $farm->name, 'farm' );
asrt( $farm->village->name, 'Dusty Mountains' );
$village = R::dispense( 'village' );
list( $mill, $tavern ) = R::dispense( 'building', 2 );
$mill->name = 'Mill';
$tavern->name = 'Tavern';
$village->ownBuilding = array( $mill, $tavern );
$id = R::store( $village );
$village = R::load( 'village', $id );
asrt( count( $village->ownBuilding ), 2 );
$village2 = R::dispense( 'village' );
$army = R::dispense( 'army' );
$village->sharedArmy[] = $army;
$village2->sharedArmy[] = $army;
$id1 = R::store( $village );
$id2 = R::store( $village2 );
$village1 = R::load( 'village', $id1 );
$village2 = R::load( 'village', $id2 );
asrt( count( $village1->sharedArmy ), 1 );
asrt( count( $village2->sharedArmy ), 1 );
asrt( count( $village1->ownArmy ), 0 );
asrt( count( $village2->ownArmy ), 0 );
}
/**
* Tests whether aliasing plays nice with beautification.
* Ensure that aliased column aren't beautified
*
* @return void
*/
public function testAliasWithBeautify()
{
$points = R::dispense( 'point', 2 );
$line = R::dispense( 'line' );
$line->pointA = $points[0];
$line->pointB = $points[1];
R::store( $line );
$line2 = R::dispense( 'line' );
$line2->pointA = $line->fetchAs('point')->pointA;
$line2->pointB = R::dispense( 'point' );
R::store( $line2 );
//now we have two points per line (1-to-x)
//I want to know which lines cross A:
$a = R::load( 'point', $line->pointA->id ); //reload A
$lines = $a->alias( 'pointA' )->ownLine;
asrt( count( $lines ), 2 );
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Arrays
*
* Beans can also be treated like arrays, this test verifies
* whether the bean array interface works correctly in various
* scenarios (combination with lists and so on).
*
* @file RedUNIT/Base/Arrays.php
* @desc Tests the array interface of beans
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Arrays extends Base
{
/**
* Tests basic array access.
*
* @return void
*/
public function testArrayAccess()
{
$bean = R::dispense('bean');
$bean->name = 'bean';
$bean->taste = 'salty';
$properties = array();
foreach($bean as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $bean ), 3 );
asrt( count( $properties ), 3 );
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['name'] ), TRUE );
asrt( isset( $properties['taste'] ), TRUE );
$bean = R::dispense('bean');
$bean['name'] = 'bean';
$bean['taste'] = 'salty';
$properties = array();
foreach($bean as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $bean ), 3 );
asrt( count( $properties ), 3 );
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['name'] ), TRUE );
asrt( isset( $properties['taste'] ), TRUE );
}
/**
* Tests array access with lists.
* Tests whether list properties behave as arrays correctly.
*
* @return void
*/
public function testArrayAccessAndLists()
{
$book = R::dispense('book');
$book['title'] = 'My Book';
//Can we add a bean in list with array access?
$book['ownPage'][] = R::dispense('page');
$book['ownPage'][] = R::dispense('page');
asrt( count( $book ), 3 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $properties ), 3 );
//Dont reveal aliased x-own and -List in foreach-loop
asrt( isset( $properties['id'] ), TRUE );
asrt( isset( $properties['title'] ), TRUE );
asrt( isset( $properties['ownPage'] ), TRUE );
asrt( isset( $properties['ownPageList'] ), FALSE );
asrt( isset( $properties['xownPage'] ), FALSE );
asrt( isset( $properties['xownPageList'] ), FALSE );
//But keep them countable
asrt( count( $book['ownPage'] ), 2 );
asrt( count( $book['ownPageList'] ), 2 );
asrt( count( $book['xownPage'] ), 2 );
asrt( count( $book['xownPageList'] ), 2 );
//And reveal state of items with isset()
asrt( isset( $book['id'] ), TRUE );
asrt( isset( $book['title'] ), TRUE );
asrt( isset( $book['ownPage'] ), TRUE );
asrt( isset( $book['ownPageList'] ), TRUE );
asrt( isset( $book['xownPage'] ), TRUE );
asrt( isset( $book['xownPageList'] ), TRUE );
//Can we add using the List alias?
$book['ownPageList'][] = R::dispense('page');
asrt( count( $book['ownPage'] ), 3 );
asrt( count( $book['ownPageList'] ), 3 );
asrt( count( $book['xownPage'] ), 3 );
asrt( count( $book['xownPageList'] ), 3 );
//Can we add using the x-mode alias?
$book['ownPageList'][] = R::dispense('page');
asrt( count( $book['ownPage'] ), 4 );
asrt( count( $book['ownPageList'] ), 4 );
asrt( count( $book['xownPage'] ), 4 );
asrt( count( $book['xownPageList'] ), 4 );
//Can we unset using array access?
unset( $book['ownPage'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['xownPage'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['ownPageList'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
$book['ownPage'] = array( R::dispense('page') );
unset( $book['xownPageList'] );
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
//does it work with shared lists as well?
$book['sharedCategory'] = array( R::dispense('category') );
asrt( count( $book ), 3 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( isset( $properties['sharedCategory'] ), TRUE );
asrt( isset( $properties['sharedCategoryList'] ), FALSE );
asrt( isset( $book['sharedCategory'] ), TRUE );
asrt( isset( $book['sharedCategoryList'] ), TRUE );
asrt( count( $book['sharedCategory'] ), 1 );
asrt( count( $book['sharedCategoryList'] ), 1 );
$book['sharedCategory'][] = R::dispense( 'category' );
asrt( count( $book['sharedCategory'] ), 2 );
asrt( count( $book['sharedCategoryList'] ), 2 );
$book['sharedCategoryList'][] = R::dispense( 'category' );
asrt( count( $book['sharedCategory'] ), 3 );
asrt( count( $book['sharedCategoryList'] ), 3 );
}
/**
* Tests array access with parent beans.
*
* @return void
*/
public function testArrayAccessWithBeans()
{
$book = R::dispense( 'bean' );
$book['author'] = R::dispense( 'author' );
asrt( isset( $book['author'] ), TRUE );
asrt( count( $book ), 2 );
$book['author']['name'] = 'me';
asrt( $book['author']['name'], 'me' );
$book['author']['address'] = R::dispense( 'address' );
$book['author']['ownTagList'][] = R::dispense( 'tag' );
asrt( isset( $book['author']['address'] ), TRUE );
asrt( isset( $book['author']['ownTag'] ), TRUE );
asrt( count( $book['author']['ownTag'] ), 1 );
asrt( isset( $book['author']['xownTag'] ), TRUE );
asrt( count( $book['author']['xownTag'] ), 1 );
asrt( isset( $book['author']['ownTagList'] ), TRUE );
asrt( count( $book['author']['ownTagList'] ), 1 );
asrt( isset( $book['author']['xownTagList'] ), TRUE );
asrt( count( $book['author']['xownTagList'] ), 1 );
unset( $book['author'] );
asrt( isset( $book['author'] ), FALSE );
asrt( count( $book ), 1 );
}
/**
* Tests array access with CRUD operations.
*
* @return void
*/
public function testArrayAccessWithCRUD()
{
R::nuke();
$book = R::dispense( 'book' );
$book['ownPageList'] = R::dispense( 'page', 3 );
R::store( $book );
$book = $book->fresh();
//note that isset first returns FALSE, so you can check if a list is loaded
asrt( isset( $book['ownPage'] ), FALSE );
asrt( isset( $book['ownPageList'] ), FALSE );
asrt( isset( $book['xownPage'] ), FALSE );
asrt( isset( $book['xownPageList'] ), FALSE );
//count triggers load...
asrt( count( $book['ownPage'] ), 3 );
asrt( isset( $book['ownPage'] ), TRUE );
asrt( isset( $book['ownPageList'] ), TRUE );
asrt( isset( $book['xownPage'] ), TRUE );
asrt( isset( $book['xownPageList'] ), TRUE );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 3 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 3 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 3 );
$book['ownPage'][] = R::dispense( 'page' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['ownPage'] ), 4 );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 4 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 4 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 4 );
//does dependency still work?
$book['xownPageList'] = array();
R::store( $book );
$book = $book->fresh();
asrt( count( $book['ownPage'] ), 0 );
$book = $book->fresh();
asrt( count( $book['xownPage'] ), 0 );
$book = $book->fresh();
asrt( count( $book['ownPageList'] ), 0 );
$book = $book->fresh();
asrt( count( $book['xownPageList'] ), 0 );
//does shared list work as well?
$book['sharedTag'] = R::dispense( 'tag', 2 );
R::store( $book );
$book = $book->fresh();
//note that isset first returns FALSE, so you can check if a list is loaded
asrt( isset( $book['sharedTagList'] ), FALSE );
asrt( isset( $book['sharedTag'] ), FALSE );
//count triggers load...
asrt( count( $book['sharedTagList'] ), 2 );
asrt( count( $book['sharedTag'] ), 2 );
asrt( isset( $book['sharedTagList'] ), TRUE );
asrt( isset( $book['sharedTag'] ), TRUE );
$book['sharedTag'][] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['sharedTagList'] ), 3 );
asrt( count( $book['sharedTag'] ), 3 );
$book['sharedTagList'][] = R::dispense( 'tag' );
R::store( $book );
$book = $book->fresh();
asrt( count( $book['sharedTagList'] ), 4 );
asrt( count( $book['sharedTag'] ), 4 );
//does it also work with cross-shared
$book['sharedBookList'][] = R::dispense( 'book' );
R::store( $book );
$book = $book->fresh();
asrt( isset( $book['sharedBookList'] ), FALSE );
asrt( count( $book['sharedBookList'] ), 1 );
$first = reset( $book['sharedBookList'] );
$id = $first['id'];
asrt( count( $book['sharedBookList'][$id]['sharedBookList'] ), 1 );
$properties = array();
foreach($book as $key => $value) {
$properties[ $key ] = $value;
}
asrt( count( $properties ), 2 );
$keys = array_keys( $properties );
sort( $keys );
asrt( implode( ',', $keys ), 'id,sharedBook' );
}
}

View File

@ -0,0 +1,296 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\OODB as OODB;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Association
*
* Originally meant to test R::associate - which is no longer
* used, this class tests all kinds of relations from
* one-to-one to polymorph relations using the poly() method.
*
* @file RedUNIT/Base/Association.php
* @desc Tests Association API (N:N associations)
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Association extends Base
{
/**
* MySQL specific tests.
* Test MySQL specific issues with constraints.
*
* @return void
*/
public function testMySQL()
{
if ( $this->currentlyActiveDriverID !== 'mysql' ) {
return;
}
testpack( 'Throw exception in case of issue with assoc constraint' );
$bunny = R::dispense( 'bunny' );
$carrot = R::dispense( 'carrot' );
$faultyWriter = new \FaultyWriter( R::getToolBox()->getDatabaseAdapter() );
$faultyOODB = new OODB( $faultyWriter );
$faultyOODB->setBeanHelper( R::getRedBean()->getBeanHelper() );
$faultyToolbox = new ToolBox( $faultyOODB, R::getToolBox()->getDatabaseAdapter(), $faultyWriter );
$faultyAssociationManager = new AssociationManager( $faultyToolbox );
$faultyWriter->setSQLState( '23000' );
$faultyAssociationManager->associate( $bunny, $carrot );
pass();
$faultyWriter->setSQLState( '42S22' );
R::nuke();
try {
$faultyAssociationManager->associate( $bunny, $carrot );
fail();
} catch ( SQL $exception ) {
pass();
}
}
/**
* Test fast-track deletion, i.e. bypassing FUSE.
* For link beans.
*
* @return void
*/
public function testFastTrackDeletion()
{
testpack( 'Test fast-track deletion' );
$ghost = R::dispense( 'ghost' );
$house = R::dispense( 'house' );
$house->sharedGhost[] = $ghost;
R::store($house);
\Model_Ghost_House::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $house, $ghost );
// No fast-track, assoc bean got trashed
asrt( \Model_Ghost_House::$deleted, TRUE );
\Model_Ghost_House::$deleted = FALSE;
$house->sharedGhost[] = $ghost;
R::store($house);
R::getRedBean()->getAssociationManager()->unassociate( $house, $ghost, TRUE );
// Fast-track, assoc bean got deleted right away
asrt( \Model_Ghost_House::$deleted, FALSE );
}
/**
* Test self-referential associations.
*
* @return void
*/
public function testCrossAssociation()
{
$ghost = R::dispense( 'ghost' );
$ghost2 = R::dispense( 'ghost' );
R::getRedBean()->getAssociationManager()->associate( $ghost, $ghost2 );
\Model_Ghost_Ghost::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $ghost, $ghost2 );
// No fast-track, assoc bean got trashed
asrt( \Model_Ghost_Ghost::$deleted, TRUE );
\Model_Ghost_Ghost::$deleted = FALSE;
R::getRedBean()->getAssociationManager()->unassociate( $ghost, $ghost2, TRUE );
// Fast-track, assoc bean got deleted right away
asrt( \Model_Ghost_Ghost::$deleted, FALSE );
}
/**
* Test limited support for polymorph associations.
* RedBeanPHP does not really feature polymorph relations since
* they are not really compatible with traditional relational databases.
* However a light-weight, basic implementation has been added for
* those circumstances where you can't live without...
* i.e... possible legacy systems and so on.
*
* @return void
*/
public function testPoly()
{
testpack( 'Testing poly' );
$shoe = R::dispense( 'shoe' );
$lace = R::dispense( 'lace' );
$lace->color = 'white';
$id = R::store( $lace );
$shoe->itemType = 'lace';
$shoe->item = $lace;
$id = R::store( $shoe );
$shoe = R::load( 'shoe', $id );
$x = $shoe->poly( 'itemType' )->item;
asrt( $x->color, 'white' );
}
/**
* Test limited support for 1-to-1 associations.
* The rule is, one-to-ones are supposes to be in the same table,
* this is just for some legacy tables not designed to work
* with RedBeanPHP at all.
*
* @return void
*/
public function testOneToOne()
{
testpack( 'Testing one-to-ones' );
$author = R::dispense( 'author' )->setAttr( 'name', 'a' );;
$bio = R::dispense( 'bio' )->setAttr( 'name', 'a' );
R::storeAll( array( $author, $bio ) );
$id1 = $author->id;
$author = R::dispense( 'author' )->setAttr( 'name', 'b' );;
$bio = R::dispense( 'bio' )->setAttr( 'name', 'b' );
R::storeAll( array( $author, $bio ) );
$x = $author->one( 'bio' );
$y = $bio->one('author');
asrt( $x->name, $bio->name );
asrt( $y->name, $author->name );
asrt( $x->id, $bio->id );
asrt( $y->id, $author->id );
$id2 = $author->id;
list( $a, $b ) = R::loadMulti( 'author,bio', $id1 );
asrt( $a->name, $b->name );
asrt( $a->name, 'a' );
list( $a, $b ) = R::loadMulti( 'author,bio', $id2 );
asrt( $a->name, $b->name );
asrt( $a->name, 'b' );
list( $a, $b ) = R::loadMulti( array( 'author', 'bio' ), $id1 );
asrt( $a->name, $b->name );
asrt( $a->name, 'a' );
list( $a, $b ) = R::loadMulti( array( 'author', 'bio' ), $id2 );
asrt( $a->name, $b->name );
asrt( $a->name, 'b' );
asrt( is_array( R::loadMulti( NULL, 1 ) ), TRUE );
asrt( ( count( R::loadMulti( NULL, 1 ) ) === 0 ), TRUE );
}
/**
* Test single column bases unique constraints.
*
* @return void
*/
public function testSingleColUniqueConstraint()
{
testpack( 'Testing unique constraint on single column' );
$book = R::dispense( 'book' );
$book->title = 'bla';
$book->extra = 2;
$id = R::store( $book );
R::getWriter()->addUniqueIndex( 'book', array( 'title' ) );
$book = R::dispense( 'book' );
$book->title = 'bla';
$expected = NULL;
try {
R::store( $book );
fail();
} catch ( SQL $e ) {
$expected = $e;
}
asrt( ( $expected instanceof SQL ), TRUE );
asrt( R::count( 'book' ), 1 );
$book = R::load( 'book', $id );
// Causes failure, table will be rebuild
$book->extra = 'CHANGE';
$id2 = R::store( $book );
$book2 = R::load( 'book', $id2 );
$book = R::dispense( 'book' );
$book->title = 'bla';
try {
R::store( $book );
fail();
} catch ( SQL $e ) {
$expected = $e;
}
asrt( ( $expected instanceof SQL ), TRUE );
asrt( R::count( 'book' ), 1 );
}
/**
* Test multiple assiociation.
*
* @return void
*/
public function testMultiAssociationDissociation()
{
$wines = R::dispense( 'wine', 3 );
$cheese = R::dispense( 'cheese', 3 );
$olives = R::dispense( 'olive', 3 );
R::getRedBean()->getAssociationManager()->associate( $wines, array_merge( $cheese, $olives ) );
asrt( R::count( 'cheese' ), 3 );
asrt( R::count( 'olive' ), 3 );
asrt( R::count( 'wine' ), 3 );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 3 );
asrt( count( $wines[1]->sharedCheese ), 3 );
asrt( count( $wines[1]->sharedOlive ), 3 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 3 );
R::getRedBean()->getAssociationManager()->unassociate( $wines, $olives );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 3 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 0 );
R::getRedBean()->getAssociationManager()->unassociate( array( $wines[1] ), $cheese );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 0 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 3 );
asrt( count( $wines[2]->sharedOlive ), 0 );
R::getRedBean()->getAssociationManager()->unassociate( array( $wines[2] ), $cheese );
asrt( count( $wines[0]->sharedCheese ), 3 );
asrt( count( $wines[0]->sharedOlive ), 0 );
asrt( count( $wines[1]->sharedCheese ), 0 );
asrt( count( $wines[1]->sharedOlive ), 0 );
asrt( count( $wines[2]->sharedCheese ), 0 );
asrt( count( $wines[2]->sharedOlive ), 0 );
}
/**
* Tests error handling related to association.
* On database systems providing informative SQL STATE error codes
* RedBeanPHP should not mind non-existing tables or columns in
* fluid mode.
*
* @return void
*/
public function testErrorHandling()
{
R::nuke();
list( $book, $page ) = R::dispenseAll( 'book,page' );
$book->sharedPage[] = $page;
R::store( $page );
$redbean = R::getRedBean();
$am = $redbean->getAssociationManager();
//SQLite and CUBRID do not comply with ANSI SQLState codes.
$catchAll = ( $this->currentlyActiveDriverID == 'sqlite' || $this->currentlyActiveDriverID === 'CUBRID' );
try {
$am->related( $book, 'page', 'invalid SQL' );
if ($catchAll) pass(); else fail();
} catch ( SQL $e ) {
if ($catchAll) fail(); else pass();
}
try {
$am->related( $book, 'cover');
pass();
} catch ( SQL $e ) {
fail();
}
try {
$am->related( R::dispense('cover'), 'book' );
pass();
} catch ( SQL $e ) {
fail();
}
}
}

View File

@ -0,0 +1,151 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\RedException\SQL as SQL;
/**
* Batch
*
* Test whether we can load a group of beans in one go, i.e.
* the batch loading functionality of RedBeanPHP, also in combination
* with aliasing.
*
* @file RedUNIT/Base/Batch.php
* @desc Tests batch loading of beans, i.e. loading large collections of beans in optimized way.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Batch extends Base
{
/**
* Can we delete beans by find-query?
*
* @return void
*/
public function testHunt()
{
R::nuke();
$books = R::dispenseAll( 'book*3' );
R::storeAll( $books[0] );
pass();
asrt( ( R::count( 'book' ) === 3 ), TRUE );
$ids = R::getCol( 'SELECT id FROM book' );
R::hunt( 'book', ' id IN ( '. R::genSlots( $ids ) .' ) ', $ids );
asrt( ( R::count( 'book' ) === 0 ), TRUE );
}
/**
* Tests batch trashing. Can we trash beans using
* IDs only?
*
* @return void
*/
public function testBatchTrash()
{
R::nuke();
$books = R::dispenseAll( 'book*3' );
R::storeAll( $books[0] );
pass();
asrt( ( R::count( 'book' ) === 3 ), TRUE );
R::trashBatch( 'book', R::getCol( 'SELECT id FROM book' ) );
asrt( ( R::count( 'book' ) === 0 ), TRUE );
}
/**
* Begin testing.
* This method runs the actual test pack.
*
* @return void
*/
public function testBatch()
{
R::freeze( FALSE );
$toolbox = R::getToolBox();
$adapter = $toolbox->getDatabaseAdapter();
$writer = $toolbox->getWriter();
$redbean = $toolbox->getRedBean();
$pdo = $adapter->getDatabase();
$page = $redbean->dispense( "page" );
$page->name = "page no. 1";
$page->rating = 1;
$id1 = $redbean->store( $page );
$page = $redbean->dispense( "page" );
$page->name = "page no. 2";
$id2 = $redbean->store( $page );
$batch = $redbean->batch( "page", array( $id1, $id2 ) );
asrt( count( $batch ), 2 );
asrt( $batch[$id1]->getMeta( "type" ), "page" );
asrt( $batch[$id2]->getMeta( "type" ), "page" );
asrt( (int) $batch[$id1]->id, $id1 );
asrt( (int) $batch[$id2]->id, $id2 );
$book = $redbean->dispense( "book" );
$book->name = "book 1";
$redbean->store( $book );
$book = $redbean->dispense( "book" );
$book->name = "book 2";
$redbean->store( $book );
$book = $redbean->dispense( "book" );
$book->name = "book 3";
$redbean->store( $book );
$books = $redbean->batch( "book", $adapter->getCol( "SELECT id FROM book" ) );
asrt( count( $books ), 3 );
$a = $redbean->batch( 'book', 9919 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
$a = $redbean->batch( 'triangle', 1 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
R::freeze( TRUE );
$a = $redbean->batch( 'book', 9919 );
asrt( is_array( $a ), TRUE );
asrt( count( $a ), 0 );
try {
$a = $redbean->batch( 'triangle', 1 );
fail();
} catch(SQL $e) {
pass();
}
R::freeze( FALSE );
asrt( R::wipe( 'spaghettimonster' ), FALSE );
}
/**
* Test missing bean scenarios.
*
* @return void
*/
public function testMissingBeans()
{
testpack( 'deal with missing beans' );
$id = R::store( R::dispense( 'beer' ) );
$bottles = R::batch( 'beer', array( $id, $id + 1, $id + 2 ) );
asrt( count( $bottles ), 3 );
asrt( (int) $bottles[$id]->id, (int) $id );
asrt( (int) $bottles[$id + 1]->id, 0 );
asrt( (int) $bottles[$id + 2]->id, 0 );
}
/**
* Test batch alias loadAll.
*
* @return void
*/
public function testBatchAliasLoadAll()
{
$ids = R::storeAll( R::dispense( 'page', 2 ) );
$pages = R::loadAll( 'page', $ids );
asrt( is_array( $pages ), TRUE );
asrt( count( $pages ), 2 );
asrt( ( $pages[$ids[0]] instanceof OODBBean ), TRUE );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\SimpleModel as SimpleModel;
/**
* Boxing
*
* Test boxing and unboxing of beans.
*
* @file RedUNIT/Base/Boxing.php
* @desc Tests bean boxing and unboxing functionality.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Boxing extends Base
{
/**
* Test boxing beans.
*
* @return void
*/
public function testBoxing()
{
R::nuke();
$bean = R::dispense( 'boxedbean' )->box();
R::trash( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->sharedBoxbean = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->ownBoxedbean = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->other = R::dispense( 'boxedbean' )->box();
R::store( $bean );
pass();
$bean = R::dispense( 'boxedbean' );
$bean->title = 'MyBean';
$box = $bean->box();
asrt( ( $box instanceof \Model_Boxedbean ), TRUE );
R::store( $box );
}
/**
* Test fix for issue #512 - thanks for reporting Bernhard H.
* OODBBean::__toString() implementation only works with C_ERR_IGNORE
*
* @return void
*/
public function testToStringIssue512()
{
R::setErrorHandlingFUSE( \RedBeanPHP\OODBBean::C_ERR_FATAL );
$boxedBean = R::dispense( 'boxedbean' );
$str = (string) $boxedBean;
asrt( $str, '{"id":0}' ); //no fatal error
R::setErrorHandlingFUSE( FALSE );
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
/**
* Chill
*
* Tests 'chill' mode. In this mode some bean types are frozen,
* their schemas cannot be modified while others are fluid and
* can still be adjusted.
*
* @file RedUNIT/Base/Chill.php
* @desc Tests chill list functionality, i.e. freezing a subset of all types.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Chill extends Base
{
/**
* Test Chill mode.
*
* @return void
*/
public function testChill()
{
$bean = R::dispense( 'bean' );
$bean->col1 = '1';
$bean->col2 = '2';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 3 );
$bean->col3 = '3';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 4 );
R::freeze( array( 'umbrella' ) );
$bean->col4 = '4';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 5 );
R::freeze( array( 'bean' ) );
$bean->col5 = '5';
try {
R::store( $bean );
fail();
} catch (\Exception $e ) {
pass();
}
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 5 );
R::freeze( array() );
$bean->col5 = '5';
R::store( $bean );
asrt( count( R::getWriter()->getColumns( 'bean' ) ), 6 );
}
/**
* Test whether we cannot add unique constraints on chilled tables,
* otherwise you cannot avoid this from happening when adding beans to the
* shared list :) -- this is almost a theoretical issue however we want it
* to work according to specifications!
*
* @return void
*/
public function testDontAddUniqueConstraintForChilledBeanTypes()
{
R::nuke();
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$person->sharedRole[] = $role;
R::store( $person );
$person->sharedRole[] = R::dispense( 'role' );
R::store( $person );
$bean = R::getRedBean()->dispense('person_role');
$bean->personId = $person->id;
$bean->roleId = $role->id;
try {
R::store( $bean );
fail();
} catch(\Exception $e) {
pass();
}
asrt(R::count('person_role'), 2);
R::nuke();
$link = R::getRedBean()->dispense('person_role');
$person = R::dispense( 'person' );
$role = R::dispense( 'role' );
$link->person = $person;
$link->role = $role;
R::store( $link );
R::freeze(array('person_role'));
$person->sharedRole[] = R::dispense( 'role' );
R::store( $person );
$bean = R::getRedBean()->dispense('person_role');
$bean->personId = $person->id;
$bean->roleId = $role->id;
try {
R::store( $bean );
pass();
} catch(\Exception $e) {
fail();
}
asrt(R::count('person_role'), 3);
R::freeze( array() ); //set freeze to FALSE and clear CHILL LIST!
}
/**
* Test whether we can set and reset the chill list and check the contents
* of the chill list.
*
* @return void
*/
public function testChillTest()
{
R::freeze( array( 'beer' ) );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( FALSE );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( TRUE );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), TRUE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
R::freeze( array() );
$oodb = R::getRedBean();
asrt( $oodb->isChilled( 'beer' ), FALSE );
asrt( $oodb->isChilled( 'wine' ), FALSE );
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\ToolBox as ToolBox;
use RedBeanPHP\QueryWriter\SQLiteT as SQLiteT;
/**
* Close
*
* Tests whether we can close the database connection.
*
* @file RedUNIT/Base/Close.php
* @desc Tests database closing functionality.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Close extends Base
{
/**
* Test closing database connection.
*
* @return void
*/
public function testClose()
{
// Test whether we can select a specific feature set
R::useFeatureSet('novice/latest');
pass();
R::useFeatureSet('latest');
pass();
R::useFeatureSet('5.3');
pass();
R::useFeatureSet('novice/5.3');
pass();
try {
R::useFeatureSet('5.2');
fail();
} catch ( \Exception $e ) {
asrt( $e->getMessage(), 'Unknown feature set label.' );
}
try {
R::nuke();
fail();
} catch( \Exception $e ) {
asrt( $e->getMessage(), 'The nuke() command has been disabled using noNuke() or R::feature(novice/...).' );
}
R::useFeatureSet('latest');
//Close
R::getDatabaseAdapter()->setOption( 'setInitQuery', NULL );
asrt( R::getDatabaseAdapter()->getDatabase()->isConnected(), TRUE );
R::close();
asrt( R::getDatabaseAdapter()->getDatabase()->isConnected(), FALSE );
}
}

View File

@ -0,0 +1,314 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
use RedBeanPHP\QueryWriter\AQueryWriter;
/**
* Concurrency
*
* Tests whether we can lock beans.
*
* @file RedUNIT/Base/Concurrency.php
* @desc Tests concurrency scenarios
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Concurrency extends Base
{
/**
* Returns the target drivers for this test.
* This test only works with Postgres and MySQL/MariaDB.
*
* @return array
*/
public function getTargetDrivers()
{
return array( 'pgsql','mysql' );
}
/**
* Prepares the database connection.
*
* @return void
*/
public function prepare()
{
try{
R::close();
} catch( \Exception $e ) {}
}
/**
* This test has to be run manually.
*
* @return void
*/
private function testLockException()
{
R::nuke();
$lock = R::dispense('lock');
$lock->value = 123;
R::store($lock);
$c = pcntl_fork();
if ($c == -1) exit(1);
if (!$c) {
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
R::freeze(TRUE);
try { R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::begin();
$lock = R::loadForUpdate('lock', $lock->id);
$lock->value = 4;
sleep(10);
R::store($lock);
exit(0);
} else {
R::selectDatabase($this->currentlyActiveDriverID . 'c2');
sleep(1);
R::freeze(TRUE);
try{ R::exec('SET SESSION innodb_lock_wait_timeout=5');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '1s';");}catch( \Exception $e ){}
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::begin();
$exception = NULL;
try {
$lock = R::loadForUpdate('lock', $lock->id);
} catch( \Exception $e ) {
$exception = $e;
}
if ( !$exception ) fail();
pass();
$details = $exception->getDriverDetails();
asrt( ($details[1]===1205 || $details[0]==='55P03'), TRUE );
var_dump($lock);
}
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
pcntl_wait($status);
try { R::exec('SET SESSION innodb_lock_wait_timeout=50');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '50s';");}catch( \Exception $e ){}
}
/**
* Tests basic locking scenarios using fork().
*
* @return void
*/
public function testConcurrency()
{
$c = pcntl_fork();
if ($c == -1) exit(1);
if (!$c) {
R::selectDatabase($this->currentlyActiveDriverID . 'c');
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
sleep(2);
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::freeze(TRUE);
R::begin();
echo "CHILD: SUBTRACTING 2 START\n";
$i = R::loadForUpdate('inventory', 1);
$i->apples -= 2;
sleep(4);
R::store($i);
R::commit();
echo "CHILD: SUBTRACTING 2 DONE\n";
echo (R::load('inventory', 1));
echo "\n";
exit(0);
} else {
R::selectDatabase($this->currentlyActiveDriverID . 'c');
try{ R::exec('SET SESSION innodb_lock_wait_timeout=51');}catch( \Exception $e ){}
try{R::exec("SET lock_timeout = '51s';");}catch( \Exception $e ){}
echo "PARENT: PREP START\n";
R::nuke();
$i = R::dispense('inventory');
$i->apples = 10;
R::store($i);
R::exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
echo "PARENT: PREP DONE\n";
sleep(3);
echo "PARENT: ADDING 5 START\n";
try { R::exec('SET autocommit = 0'); }catch( \Exception $e ){}
R::freeze( TRUE );
R::begin();
$i = R::findForUpdate('inventory', ' id = ? ', array(1));
$i = reset( $i );
print_r($i);
$i->apples += 5;
R::store($i);
R::commit();
echo "PARENT ADDING 5 DONE\n";
$i = R::getAll('select * from inventory where id = 1');
print_r($i);
asrt((int)$i[0]['apples'], 13);
R::freeze( FALSE );
try { R::exec('SET autocommit = 1'); }catch( \Exception $e ){}
pcntl_wait($status);
}
}
/**
* Test whether we can use setSQLSnippet with find() and batch().
*
* @return void
*/
public function testBatchAndFind()
{
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* findOneForUpdate */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::findOneForUpdate( 'bean' );
asrt( count( $beans ), 1 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* findForUpdate */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::findForUpdate( 'bean' );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* batch + snippet */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
R::getWriter()->setSQLSelectSnippet('for update');
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 1 );
print_r( $entries );
$logs->clear();
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
/* find + snippet */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
R::getWriter()->setSQLSelectSnippet('for update');
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::find( 'bean' );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 1 );
print_r( $entries );
$logs->clear();
/* baseline */
R::nuke();
$beans = R::dispenseAll('bean*10');
R::storeAll($beans[0]);
$ids = array();
foreach($beans[0] as $bean) $ids[] = $bean->id;
R::debug( TRUE, 1 );
$logs = R::getDatabaseAdapter()
->getDatabase()
->getLogger();
$beans = R::batch( 'bean', $ids );
asrt( count( $beans ), 10 );
$entries = $logs->grep('for update');
asrt( count( $entries ), 0 );
$logs->clear();
R::debug( FALSE );
}
/**
* loadForUpdate/findForUpdate should be applied even if caching is on.
* Caching may not interfere with locking beans.
*
* @return void
*/
public function testLockAndCache()
{
R::nuke();
$bean = R::dispense('lock');
$bean->title = 'lock';
$id = R::store( $bean );
R::getWriter()->setUseCache( TRUE );
$lock = R::loadForUpdate( 'lock', $id );
R::debug( TRUE, 1 );
$lock = R::loadForUpdate( 'lock', $id );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
R::debug( FALSE );
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
R::debug( TRUE, 1 );
$lock = R::findForUpdate( 'lock', 'id = ?', array( $id ) );
$logs = R::getDatabaseAdapter()->getDatabase()->getLogger()->grep( AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
asrt( count($logs), 1 ); //if no cache clear, then would have been 2
R::getWriter()->setUseCache( FALSE );
R::debug( FALSE );
R::nuke();
}
}

View File

@ -0,0 +1,224 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Copy
*
* Tests whether we can make a copy or a deep copy of a bean
* and whether recursion is handled well. Also tests
* versioning: copying can be used to implement a versioning feature,
* some test cases will reflect this particular use case.
*
* @file RedUNIT/Base/Copy.php
* @desc Tests whether we can make a deep copy of a bean.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Copy extends Base
{
/**
* Test whether recursion happens
*/
public function testCopyRecursion()
{
$document = R::dispense( 'document' );
$id = R::store( $document );
$document->ownDocument[] = $document;
R::store( $document );
$duplicate = R::dup( $document );
pass(); //if RB cant handle this is will crash (nesting level error from PHP).
$id2 = R::store( $duplicate );
$duplicate = R::load( 'document', $id );
asrt( (int) $document->document_id, $id );
asrt( (int) $duplicate->document_id, $id2 );
// Export variant
$duplicate = R::exportAll( $document );
asrt( (int) $duplicate[0]['document_id'], $id );
}
/**
* Test real world scenario: Versioning
*/
public function testVersioning()
{
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $duplicate );
$duplicate = R::dup( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 1 );
asrt( R::count( 'user' ), 1 );
asrt( R::count( 'document' ), 3 );
asrt( R::count( 'page' ), 3 );
asrt( R::count( 'spaceship' ), 0 );
}
/**
* Same as above but now with intermediate save, counts must be same
*/
public function testVersioningIntermediateSaves()
{
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 1 );
asrt( R::count( 'user' ), 1 );
asrt( R::count( 'document' ), 3 );
asrt( R::count( 'page' ), 3 );
asrt( R::count( 'spaceship' ), 0 );
// same, but now with intermediate save, counts must be same
R::freeze( TRUE );
$document = R::dispense( 'document' );
$page = R::dispense( 'page' );
$document->title = 'test';
$page->content = 'lorem ipsum';
$user = R::dispense( 'user' );
$user->name = 'Leo';
$document->sharedUser[] = $user;
$document->ownPage[] = $page;
$document->starship_id = 3;
$document->planet = R::dispense( 'planet' );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
R::store( $document );
$duplicate = R::dup( $document );
R::store( $document );
R::store( $duplicate );
asrt( R::count( 'planet' ), 2 );
asrt( R::count( 'user' ), 2 );
asrt( R::count( 'document' ), 6 );
asrt( R::count( 'page' ), 6 );
try { asrt( R::count( 'spaceship' ), 0 ); }catch(\Exception $e){pass();}
R::freeze( FALSE );
}
/**
* Test Recursion
*/
public function testRecursion()
{
list( $d1, $d2 ) = R::dispense( 'document', 2 );
$page = R::dispense( 'page' );
list( $p1, $p2 ) = R::dispense( 'paragraph', 2 );
list( $e1, $e2 ) = R::dispense( 'excerpt', 2 );
$id2 = R::store( $d2 );
$p1->name = 'a';
$p2->name = 'b';
$page->title = 'my page';
$page->ownParagraph = array( $p1, $p2 );
$p1->ownExcerpt[] = $e1;
$p2->ownExcerpt[] = $e2;
$e1->ownDocument[] = $d2;
$e2->ownDocument[] = $d1;
$d1->ownPage[] = $page;
$id1 = R::store( $d1 );
$d1 = R::load( 'document', $id1 );
$d = R::dup( $d1 );
$ids = array();
asrt( ( $d instanceof OODBBean ), TRUE );
asrt( count( $d->ownPage ), 1 );
foreach ( end( $d->ownPage )->ownParagraph as $p ) {
foreach ( $p->ownExcerpt as $e ) {
$ids[] = end( $e->ownDocument )->id;
}
}
sort( $ids );
asrt( (int) $ids[0], 0 );
asrt( (int) $ids[1], $id1 );
R::store( $d );
pass();
$phillies = R::dispense( 'diner' );
list( $lonelyman, $man, $woman ) = R::dispense( 'guest', 3 );
$attendant = R::dispense( 'employee' );
$lonelyman->name = 'Bennie Moten';
$man->name = 'Daddy Stovepipe';
$woman->name = 'Mississippi Sarah';
$attendant->name = 'Gus Cannon';
$phillies->sharedGuest = array( $lonelyman, $man, $woman );
$phillies->ownEmployee[] = $attendant;
$props = R::dispense( 'prop', 2 );
$props[0]->kind = 'cigarette';
$props[1]->kind = 'coffee';
$thought = R::dispense( 'thought' );
$thought->content = 'Blues';
$thought2 = R::dispense( 'thought' );
$thought2->content = 'Jazz';
$woman->ownProp[] = $props[0];
$man->sharedProp[] = $props[1];
$attendant->ownThought = array( $thought, $thought2 );
R::store( $phillies );
$diner = R::findOne( 'diner' );
$diner2 = R::dup( $diner );
$id2 = R::store( $diner2 );
$diner2 = R::load( 'diner', $id2 );
asrt( count( $diner->ownEmployee ), 1 );
asrt( count( $diner2->ownEmployee ), 1 );
asrt( count( $diner->sharedGuest ), 3 );
asrt( count( $diner2->sharedGuest ), 3 );
$employee = reset( $diner->ownEmployee );
asrt( count( $employee->ownThought ), 2 );
$employee = reset( $diner2->ownEmployee );
asrt( count( $employee->ownThought ), 2 );
// Can we change something in the duplicate without changing the original?
$employee->name = 'Marvin';
$thought = R::dispense( 'thought' );
$thought->content = 'depression';
$employee->ownThought[] = $thought;
array_pop( $diner2->sharedGuest );
$guest = reset( $diner2->sharedGuest );
$guest->name = 'Arthur Dent';
$id2 = R::store( $diner2 );
$diner2 = R::load( 'diner', $id2 );
asrt( count( $diner->ownEmployee ), 1 );
asrt( count( $diner2->ownEmployee ), 1 );
asrt( count( $diner->sharedGuest ), 3 );
asrt( count( $diner2->sharedGuest ), 2 );
$employeeOld = reset( $diner->ownEmployee );
asrt( count( $employeeOld->ownThought ), 2 );
$employee = reset( $diner2->ownEmployee );
asrt( count( $employee->ownThought ), 3 );
asrt( $employee->name, 'Marvin' );
asrt( $employeeOld->name, 'Gus Cannon' );
// However the shared beans must not be copied
asrt( R::count( 'guest' ), 3 );
asrt( R::count( 'guest_prop' ), 1 );
$arthur = R::findOne( 'guest', ' ' . R::getWriter()->esc( 'name' ) . ' = ? ', array( 'Arthur Dent' ) );
asrt( $arthur->name, 'Arthur Dent' );
}
}

View File

@ -0,0 +1,226 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\OODBBean;
/**
* Count
*
* Tests whether we can count beans with or without
* additional conditions and whether we can count associated
* beans (relationCount).
*
* @file RedUNIT/Base/Count.php
* @desc Tests for simple bean counting.
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Count extends Base
{
/**
* Tests type check and conversion in
* OODB for count().
*
* @return void
*/
public function testCountType()
{
R::nuke();
$book = R::dispense( 'book' );
$book->sharedPage = R::dispense( 'page', 10 );
R::store( $book );
asrt( R::count('bookPage'), 10 );
try {
R::count( 'WrongTypeName' );
fail();
} catch ( RedException $ex ) {
pass();
}
try {
R::count( 'wrong_type_name' );
fail();
} catch ( RedException $ex ) {
pass();
}
}
/**
* Test count and wipe.
*
* @return void
*/
public function testCountAndWipe()
{
testpack( "Test count and wipe" );
$page = R::dispense( "page" );
$page->name = "ABC";
R::store( $page );
$n1 = R::count( "page" );
$page = R::dispense( "page" );
$page->name = "DEF";
R::store( $page );
$n2 = R::count( "page" );
asrt( $n1 + 1, $n2 );
R::wipe( "page" );
asrt( R::count( "page" ), 0 );
asrt( R::getRedBean()->count( "page" ), 0 );
asrt( R::getRedBean()->count( "kazoo" ), 0 ); // non existing table
R::freeze( TRUE );
try {
asrt( R::getRedBean()->count( "kazoo" ), 0 ); // non existing table
fail();
} catch( \Exception $e ) {
pass();
}
R::freeze( FALSE );
$page = R::dispense( 'page' );
$page->name = 'foo';
R::store( $page );
$page = R::dispense( 'page' );
$page->name = 'bar';
R::store( $page );
asrt( R::count( 'page', ' name = ? ', array( 'foo' ) ), 1 );
// Now count something that does not exist, this should return 0. (just be polite)
asrt( R::count( 'teapot', ' name = ? ', array( 'flying' ) ), 0 );
asrt( R::count( 'teapot' ), 0 );
$currentDriver = $this->currentlyActiveDriverID;
// Some drivers don't support that many error codes.
if ( $currentDriver === 'mysql' || $currentDriver === 'postgres' ) {
try {
R::count( 'teaport', ' for tea ' );
fail();
} catch ( SQL $e ) {
pass();
}
}
}
/**
* Can we count the number of shared beans?
*
* @return void
*/
public function testCountShared()
{
R::nuke();
$book = R::dispense( 'book' );
$book->sharedPageList = R::dispense( 'page', 5 );
R::store( $book );
asrt( $book->countShared('page'), 5 );
asrt( $book->countShared('leaflet'), 0 );
asrt( R::dispense( 'book' )->countShared('page'), 0 );
$am = R::getRedBean()->getAssociationManager();
asrt( $am->relatedCount( R::dispense( 'book' ), 'page' ), 0);
try {
$am->relatedCount( 'not a bean', 'type' );
fail();
} catch( RedException $e ) {
pass();
}
R::getWriter()->setUseCache(TRUE);
asrt( $book->countShared('page'), 5 );
R::exec('DELETE FROM book_page WHERE book_id > 0 -- keep-cache');
asrt( $book->countShared('page'), 5 );
R::getWriter()->setUseCache(FALSE);
asrt( $book->countShared('page'), 0 );
}
/**
* Test $bean->countOwn($type);
*
* @return void
*/
public function testCountOwn()
{
R::nuke();
$book = R::dispense( 'book' );
$empty = R::dispense( 'book' );
$nothing = R::dispense( 'book' );
$page = R::dispense( 'page' );
$book->ownPageList[] = $page;
R::store( $book );
R::store( $empty );
OODBBean::useFluidCount( FALSE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
$old = OODBBean::useFluidCount( TRUE );
asrt( $old, FALSE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( TRUE );
asrt( $book->countOwn('page'), 1 );
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( FALSE );
R::nuke();
asrt( $empty->countOwn('page'), 0 );
asrt( $nothing->countOwn('page'), 0 );
R::freeze( TRUE );
asrt( $nothing->countOwn('page'), 0 );
try { asrt( $empty->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
try { asrt( $book->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
R::freeze( FALSE );
OODBBean::useFluidCount( FALSE );
try { asrt( $empty->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
try { asrt( $book->countOwn('page'), 0 ); fail(); } catch(\Exception $e) { pass(); }
OODBBean::useFluidCount( TRUE );
}
/**
* Test $bean->withCondition( ... )->countOwn( $type );
*
* @return void
*/
public function testCountWithCondition()
{
R::nuke();
$book = R::dispense( 'book' );
$book->ownPageList[] = R::dispense( 'page' );
R::store( $book );
OODBBean::useFluidCount( FALSE );
$count = $book
->withCondition(' id > :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id > ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id < :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 0 );
$count = $book
->withCondition(' id < ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 0 );
OODBBean::useFluidCount( TRUE );
$count = $book
->withCondition(' id > :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id > ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 1 );
$count = $book
->withCondition(' id < :id ', array( ':id' => 0 ) )
->countOwn('page');
asrt( $count, 0 );
$count = $book
->withCondition(' id < ? ', array( 0 ) )
->countOwn('page');
asrt( $count, 0 );
}
}

View File

@ -0,0 +1,474 @@
<?php
namespace RedUNIT\Base;
use RedUNIT\Base as Base;
use RedBeanPHP\Facade as R;
use RedBeanPHP\AssociationManager as AssociationManager;
use RedBeanPHP\RedException\SQL as SQL;
use RedBeanPHP\RedException as RedException;
use RedBeanPHP\OODBBean as OODBBean;
/**
* Cross
*
* Tests self referential many-to-many relations, including the
* aggr feature.
*
* @file RedUNIT/Base/Cross.php
* @desc Tests associations within the same table (i.e. page_page2 alike)
* Cross tables, self referential many-to-many relations
* and aggregation techniques
* @author Gabor de Mooij and the RedBeanPHP Community
* @license New BSD/GPLv2
*
* (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
* This source file is subject to the New BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class Cross extends Base
{
/**
* Test how well aggr handles fields with no
* values.
*
* @return void
*/
public function testAggrNullHandling()
{
R::nuke();
$book = R::dispense( 'book' );
$page = R::dispense( 'page' );
$page->name = 'Page 3';
$book->xownPageList[] = $page;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
pass();
asrt( count( $texts ), 0 );
asrt( is_array( $texts ), TRUE );
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense('text');
$text1->content = 'Text 1';
$page1->text = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->text = $text2;
$book->xownPageList[] = $page2;
$page3 = R::dispense( 'page' );
$page3->name = 'Page 3';
$book->xownPageList[] = $page3;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
pass();
asrt( count( $texts ), 2 );
}
/**
* Test many different scenarios with self referential
* many-to-many relations.
*
* @return void
*/
public function testSelfReferentialCRUD()
{
R::nuke();
$buddies = R::dispense( 'buddy', 4 );
$buddies[0]->name = 'A';
$buddies[1]->name = 'B';
$buddies[2]->name = 'C';
$buddies[3]->name = 'D';
$buddies[0]->sharedBuddyList = array( $buddies[1], $buddies[2] );
$buddies[3]->sharedBuddyList = array( $buddies[2] );
R::storeAll( $buddies );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
//does this yield valid combinations - cross relations / self ref n-m
//need to symmetric....
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C' );
unset( $buddies[0]->sharedBuddy );
R::storeAll( $buddies );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$buddies[3] = $buddies[3]->fresh();
asrt( count( $buddies[3]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[3]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A' );
//Can we add one?
$buddies[1]->sharedBuddyList[] = R::dispense('buddy')->setAttr('name', 'E');
R::store( $buddies[1] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,E' );
$all = R::find( 'buddy' );
asrt( count( $all ), 5 );
foreach( $buddies[1]->sharedBuddy as $buddy ) {
if ( $buddy->name === 'E' ) {
$buddyE = $buddy;
}
}
asrt( isset( $buddyE ), TRUE );
asrt( $buddyE->name, 'E' );
//can we update?
foreach( $buddies[0]->sharedBuddy as $buddy ) {
if ( $buddy->name === 'C' ) {
$buddy->name = 'C2';
}
}
R::store( $buddies[0] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B,C2' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
//can we delete?
foreach( $buddies[0]->sharedBuddyList as $id => $buddy ) {
if ( $buddy->name === 'B' ) {
unset( $buddies[0]->sharedBuddyList[$id] );
}
}
R::store( $buddies[0] );
$buddies[0] = $buddies[0]->fresh();
asrt( count( $buddies[0]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[0]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C2' );
$buddies[1] = $buddies[1]->fresh();
asrt( count( $buddies[1]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[1]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'E' );
asrt( R::count( 'buddy' ), 5 );
asrt( R::count( 'buddyBuddy' ), 3 );
$buddies[3] = $buddies[3]->fresh();
asrt( count( $buddies[3]->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddies[3]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'C2' );
$buddies[2] = $buddies[2]->fresh();
asrt( count( $buddies[2]->sharedBuddyList ), 2 );
$names = R::gatherLabels( $buddies[2]->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'A,D' );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->sharedBuddyList ), 1 );
$names = R::gatherLabels( $buddyE->sharedBuddyList );
sort( $names );
$names = implode( ',', $names );
asrt( $names, 'B' );
if ( $this->currentlyActiveDriverID === 'mysql' ) {
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->with(' HAVING linked_by > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->with(' HAVING linked_by < 0 ')->sharedBuddyList ), 0 );
}
if ( $this->currentlyActiveDriverID === 'sqlite' ) {
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' linked_by > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' linked_by < 0 ')->sharedBuddyList ), 0 );
}
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id > 0 AND buddy_buddy.buddy2_id > 0 ')->sharedBuddyList ), 1 );
$buddyE = $buddyE->fresh();
asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id < 0 AND buddy_buddy.buddy2_id < 0 ')->sharedBuddyList ), 0 );
}
/**
* Test self referential N-M relations (page_page).
*
* @return void
*/
public function testSelfReferential()
{
$page = R::dispense( 'page' )->setAttr( 'title', 'a' );
$page->sharedPage[] = R::dispense( 'page' )->setAttr( 'title', 'b' );
R::store( $page );
$page = $page->fresh();
$page = reset( $page->sharedPage );
asrt( $page->title, 'b' );
$tables = array_flip( R::inspect() );
asrt( isset( $tables['page_page'] ), TRUE );
$columns = R::inspect( 'page_page' );
asrt( isset( $columns['page2_id'] ), TRUE );
}
/**
* Test the unique constraint.
* Just want to make sure it is not too limiting and functions
* properly for typical RedBeanPHP usage.
*
* @return void
*/
public function testUnique()
{
R::nuke();
$page1 = R::dispense( 'page' );
$tag1 = R::dispense( 'tag' );
$page2 = R::dispense( 'page' );
$tag2 = R::dispense( 'tag' );
$page3 = R::dispense( 'page' );
$tag3 = R::dispense( 'tag' );
$page1->sharedTag[] = $tag1;
R::store( $page1 );
//can we save all combinations with unique?
asrt( R::count( 'pageTag' ), 1);
$page1->sharedTag[] = $tag2;
R::store( $page1 );
asrt( R::count( 'pageTag' ), 2 );
$page1->sharedTag[] = $tag3;
$page2->sharedTag[] = $tag1;
$page2->sharedTag[] = $tag2;
$page2->sharedTag[] = $tag3;
$page3->sharedTag[] = $tag1;
$page3->sharedTag[] = $tag2;
$page3->sharedTag[] = $tag3;
R::storeAll( array( $page1, $page2, $page3 ) );
asrt( R::count('pageTag'), 9 );
$page1 = $page1->fresh();
$page1->sharedTag[] = $tag3;
R::store( $page1 );
//cant add violates unique
asrt( R::count( 'pageTag' ), 9 );
}
/**
* Shared lists can only be formed using types.
* If you happen to have two beans of the same type you can still
* have a shared list but not with a sense of direction.
* I.e. quest->sharedQuest returns all the quests that follow the first one,
* but also the ones that are followed by the first one.
* If you want to have some sort of direction; i.e. one quest follows another one
* you'll have to use an alias: quest->target, but now you can't use the shared list
* anymore because it will start looking for a type named 'target' (which is just an alias)
* for quest, but it cant find that table and it's not possible to 'keep remembering'
* the alias throughout the system.
*
* The aggr() method solves this inconvenience.
* Aggr iterates through the list identified by its first argument ('target' -> ownQuestTargetList)
* and fetches every ID of the target (quest_target.target_id), loads these beans in batch for
* optimal performance, puts them back in the beans (questTarget->target) and returns the
* references.
*
* @return void
*/
public function testAggregationInSelfRefNM()
{
R::nuke();
$quest1 = R::dispense( 'quest' );
$quest1->name = 'Quest 1';
$quest2 = R::dispense( 'quest' );
$quest2->name = 'Quest 2';
$quest3 = R::dispense( 'quest' );
$quest3->name = 'Quest 3';
$quest4 = R::dispense( 'quest' );
$quest4->name = 'Quest 4';
$quest1->link( 'questTarget' )->target = $quest2;
$quest1->link( 'questTarget' )->target = $quest3;
$quest3->link( 'questTarget' )->target = $quest4;
$quest3->link( 'questTarget' )->target = $quest1;
R::storeAll( array( $quest1, $quest3 ) );
//There should be 4 links
asrt( (int) R::count('questTarget'), 4 );
$quest1 = $quest1->fresh();
$targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
//can we aggregate the targets over the link type?
asrt( count( $targets), 2 );
//are the all valid beans?
foreach( $targets as $target ) {
//are they beans?
asrt( ( $target instanceof OODBBean ), TRUE );
//are they fetched as quest?
asrt( ( $target->getMeta( 'type' ) ), 'quest' );
}
//list target should already have been loaded, nuke has no effect
R::nuke();
$links = $quest1->ownQuestTargetList;
//are the links still there, have they been set in the beans as well?
asrt( count( $links ), 2);
//are they references instead of copies, changes in the aggregation set should affect the beans in links!
foreach( $targets as $target ) {
$target->name .= 'b';
asrt( substr( $target->name, -1 ), 'b' );
}
//do the names end with a 'b' here as well ? i.e. have they been changed through references?
foreach( $links as $link ) {
asrt( substr( $target->name, -1 ), 'b' );
}
//now test the effect on existing shadow...
R::nuke();
$quest1 = R::dispense('quest');
$quest1->name = 'Quest 1';
$quest2 = R::dispense('quest');
$quest2->name = 'Quest 2';
$quest3 = R::dispense('quest');
$quest3->name = 'Quest 3';
$quest4 = R::dispense('quest');
$quest4->name = 'Quest 4';
$quest1->link( 'questTarget' )->target = $quest2;
$quest1->link( 'questTarget' )->target = $quest3;
R::store($quest1);
asrt( (int) R::count( 'questTarget' ), 2 );
//now lets first build a shadow
$quest1->link( 'questTarget' )->target = $quest4;
//$quest1 = $quest1->fresh();
$targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
//targets should not include the new bean...
asrt( count($targets), 2 );
//this should not overwrite the existing beans
R::store($quest1);
asrt( (int) R::count( 'questTarget' ), 3 );
}
/**
* Test aggr without the aliasing.
*
* @return void
*/
public function testAggrBasic()
{
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense('text');
$text1->content = 'Text 1';
$page1->text = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->text = $text2;
$book->xownPageList[] = $page2;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'text' );
R::nuke();
asrt( count( $texts ), 2 );
foreach( $texts as $text ) {
asrt( ( $text instanceof OODBBean ), TRUE );
}
$pages = $book->ownPageList;
asrt( count( $pages ), 2 );
asrt( R::count( 'page' ), 0 );
foreach( $pages as $page ) {
asrt( ( $page instanceof OODBBean ), TRUE );
$text = $page->text;
asrt( ( $text instanceof OODBBean ), TRUE );
$text->content = 'CHANGED';
}
foreach( $texts as $text ) {
asrt( $text->content, 'CHANGED' );
}
}
/**
* Test aggr with basic aliasing.
*
* @return void
*/
public function testAggrWithOnlyAlias()
{
R::nuke();
$book = R::dispense( 'book' );
$page1 = R::dispense( 'page' );
$page1->name = 'Page 1';
$text1 = R::dispense( 'text' );
$text1->content = 'Text 1';
$page1->content = $text1;
$book->xownPageList[] = $page1;
$page2 = R::dispense( 'page' );
$page2->name = 'Page 2';
$text2 = R::dispense( 'text' );
$text2->content = 'Text 2';
$page2->content = $text2;
$book->xownPageList[] = $page2;
R::store( $book );
$book = $book->fresh();
$texts = $book->aggr( 'ownPageList', 'content', 'text' );
R::nuke();
asrt( count( $texts ), 2 );
foreach( $texts as $text ) {
asrt( ( $text instanceof OODBBean), TRUE );
}
$pages = $book->ownPageList;
asrt( count( $pages ), 2 );
asrt( R::count( 'page' ), 0 );
foreach( $pages as $page ) {
asrt( ( $page instanceof OODBBean ), TRUE );
$text = $page->content;
asrt( ( $text instanceof OODBBean ), TRUE );
$text->content = 'CHANGED';
}
foreach( $texts as $text ) {
asrt( $text->content, 'CHANGED' );
}
}
/**
* The aggr method can only be used with own-list.
*
* @return void
*/
public function testErrorHandlingAggr()
{
$wrongLists = array( 'not-an-own-list', 'OWNlist', 'Ownpage', 'ownbook', 'own book', '!', 'sharedBook' );
foreach( $wrongLists as $wrongList ) {
$bean = R::dispense( 'bean' );
try {
$bean->aggr( $wrongList, 'field' );
fail();
} catch ( \Exception $exception ) {
pass();
asrt( ( $exception instanceof RedException ), TRUE );
}
}
}
}

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