organizr.class.php 224 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421
  1. <?php
  2. use Dibi\Connection;
  3. class Organizr
  4. {
  5. // Use Custom Functions From Traits;
  6. use TwoFAFunctions;
  7. use ApiFunctions;
  8. use AuthFunctions;
  9. use BackupFunctions;
  10. use ConfigFunctions;
  11. use DemoFunctions;
  12. use HomepageConnectFunctions;
  13. use HomepageFunctions;
  14. use LogFunctions;
  15. use NetDataFunctions;
  16. use NormalFunctions;
  17. use OAuthFunctions;
  18. use OptionsFunction;
  19. use OrganizrFunctions;
  20. use PluginFunctions;
  21. use StaticFunctions;
  22. use SSOFunctions;
  23. use TokenFunctions;
  24. use UpdateFunctions;
  25. use UpgradeFunctions;
  26. // Use homepage item functions
  27. use CalendarHomepageItem;
  28. use CouchPotatoHomepageItem;
  29. use DelugeHomepageItem;
  30. use EmbyHomepageItem;
  31. use HealthChecksHomepageItem;
  32. use HTMLHomepageItem;
  33. use ICalHomepageItem;
  34. use JackettHomepageItem;
  35. use JDownloaderHomepageItem;
  36. use JellyfinHomepageItem;
  37. use LidarrHomepageItem;
  38. use MiscHomepageItem;
  39. use MonitorrHomepageItem;
  40. use NetDataHomepageItem;
  41. use NZBGetHomepageItem;
  42. use OctoPrintHomepageItem;
  43. use OmbiHomepageItem;
  44. use OverseerrHomepageItem;
  45. use PiHoleHomepageItem;
  46. use PlexHomepageItem;
  47. use QBitTorrentHomepageItem;
  48. use RadarrHomepageItem;
  49. use RTorrentHomepageItem;
  50. use SabNZBdHomepageItem;
  51. use SickRageHomepageItem;
  52. use SonarrHomepageItem;
  53. use SpeedTestHomepageItem;
  54. use TautulliHomepageItem;
  55. use TraktHomepageItem;
  56. use TransmissionHomepageItem;
  57. use UnifiHomepageItem;
  58. use WeatherHomepageItem;
  59. use uTorrentHomepageItem;
  60. // ===================================
  61. // Organizr Version
  62. public $version = '2.1.730';
  63. // ===================================
  64. // Quick php Version check
  65. public $minimumPHP = '7.3';
  66. // ===================================
  67. protected $db;
  68. protected $otherDb;
  69. public $config;
  70. public $user;
  71. public $userConfigPath;
  72. public $defaultConfigPath;
  73. public $currentTime;
  74. public $docker;
  75. public $dev;
  76. public $demo;
  77. public $commit;
  78. public $fileHash;
  79. public $cookieName;
  80. public $organizrLog;
  81. public $organizrLoginLog;
  82. public $timeExecution;
  83. public $root;
  84. public $paths;
  85. public $updating;
  86. public $groupOptions;
  87. public $warnings;
  88. public $errors;
  89. public function __construct($updating = false)
  90. {
  91. // First Check PHP Version
  92. $this->checkPHP();
  93. // Check Disk Space
  94. $this->checkDiskSpace();
  95. // Constructed from Updater?
  96. $this->updating = $updating;
  97. // Set Project Root directory
  98. $this->root = dirname(__DIR__, 2);
  99. // Set Start Execution Time
  100. $this->timeExecution = $this->timeExecution();
  101. // Set location path to user config path
  102. $this->userConfigPath = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.php';
  103. // Set location path to default config path
  104. $this->defaultConfigPath = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'default.php';
  105. // Set current time
  106. $this->currentTime = gmdate("Y-m-d\TH:i:s\Z");
  107. // Set variable if install is for official docker
  108. $this->docker = (file_exists(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Docker.txt'));
  109. // Set variable if install is for develop and set php Error levels
  110. $this->dev = (file_exists(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Dev.txt'));
  111. $this->phpErrors();
  112. // Set variable if install is for demo
  113. $this->demo = (file_exists(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Demo.txt'));
  114. // Set variable if install has commit hash
  115. $this->commit = ($this->docker && !$this->dev) ? file_get_contents(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Github.txt') : null;
  116. // Set variable to be used as hash for files
  117. $this->fileHash = ($this->commit) ?? $this->version;
  118. $this->fileHash = trim($this->fileHash);
  119. // Load Config file
  120. $this->config = $this->config();
  121. // Set organizr Log file location
  122. $this->organizrLog = ($this->hasDB()) ? $this->config['dbLocation'] . 'organizrLog.json' : false;
  123. // Set organizr Login Log file location
  124. $this->organizrLoginLog = ($this->hasDB()) ? $this->config['dbLocation'] . 'organizrLoginLog.json' : false;
  125. // Set Paths
  126. $this->paths = array(
  127. 'Root Folder' => dirname(__DIR__, 2) . DIRECTORY_SEPARATOR,
  128. 'Cache Folder' => dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR,
  129. 'Tab Folder' => dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR,
  130. 'API Folder' => dirname(__DIR__, 1) . DIRECTORY_SEPARATOR,
  131. 'DB Folder' => ($this->hasDB()) ? $this->config['dbLocation'] : false
  132. );
  133. // Connect to DB
  134. $this->connectDB();
  135. // Check DB Writable
  136. $this->checkWritableDB();
  137. // Set cookie name for Organizr Instance
  138. $this->cookieName = ($this->hasDB()) ? $this->config['uuid'] !== '' ? 'organizr_token_' . $this->config['uuid'] : 'organizr_token_temp' : 'organizr_token_temp';
  139. // Get token form cookie and validate
  140. $this->setCurrentUser();
  141. // might just run this at index
  142. $this->upgradeCheck();
  143. // Is Page load Organizr OAuth?
  144. $this->checkForOrganizrOAuth();
  145. // Is user Blacklisted?
  146. $this->checkIfUserIsBlacklisted();
  147. }
  148. public function __destruct()
  149. {
  150. $this->disconnectDB();
  151. }
  152. protected function connectDB()
  153. {
  154. if ($this->hasDB()) {
  155. try {
  156. $connect = [
  157. 'driver' => 'sqlite3',
  158. 'database' => $this->config['dbLocation'] . $this->config['dbName']
  159. ];
  160. $this->db = new Connection($connect);
  161. } catch (Dibi\Exception $e) {
  162. $this->db = null;
  163. }
  164. } else {
  165. $this->db = null;
  166. }
  167. }
  168. public function disconnectDB()
  169. {
  170. if ($this->hasDB()) {
  171. $this->db->disconnect();
  172. $this->db = null;
  173. unset($this->db);
  174. }
  175. }
  176. public function connectOtherDB($file = null)
  177. {
  178. $file = $file ?? $this->config['dbLocation'] . 'tempMigration.db';
  179. try {
  180. $this->otherDb = new Connection([
  181. 'driver' => 'sqlite3',
  182. 'database' => $file,
  183. ]);
  184. } catch (Dibi\Exception $e) {
  185. $this->otherDb = null;
  186. }
  187. }
  188. public function setCurrentUser($validate = true)
  189. {
  190. $user = false;
  191. if ($this->hasDB()) {
  192. if ($this->hasCookie()) {
  193. $user = $this->getUserFromToken($_COOKIE[$this->cookieName]);
  194. }
  195. }
  196. $this->user = ($user) ?: $this->guestUser();
  197. if ($validate) {
  198. $this->checkUserTokenForValidation();
  199. }
  200. }
  201. public function checkUserTokenForValidation()
  202. {
  203. if ($this->hasDB()) {
  204. if ($this->hasCookie()) {
  205. $this->validateToken($_COOKIE[$this->cookieName]);
  206. }
  207. }
  208. }
  209. public function phpErrors()
  210. {
  211. $errorTypes = $this->dev ? E_ERROR | E_WARNING | E_PARSE | E_NOTICE : 0;
  212. $displayErrors = $this->dev ? 1 : 0;
  213. error_reporting($errorTypes);
  214. ini_set('display_errors', $displayErrors);
  215. }
  216. public function checkForOrganizrOAuth()
  217. {
  218. // Oauth?
  219. if ($this->config['authProxyEnabled'] && $this->config['authProxyHeaderName'] !== '' && $this->config['authProxyWhitelist'] !== '') {
  220. if (isset(getallheaders()[$this->config['authProxyHeaderName']])) {
  221. $this->coookieSeconds('set', 'organizrOAuth', 'true', 20000, false);
  222. }
  223. }
  224. }
  225. public function checkIfUserIsBlacklisted()
  226. {
  227. if ($this->hasDB()) {
  228. $currentIP = $this->userIP();
  229. if ($this->config['blacklisted'] !== '') {
  230. if (in_array($currentIP, $this->arrayIP($this->config['blacklisted']))) {
  231. $this->debug('User was sent to blackhole - Blacklisted IPs: ' . $this->config['blacklisted']);
  232. die($this->showHTML('Blacklisted', $this->config['blacklistedMessage']));
  233. }
  234. }
  235. }
  236. }
  237. public function checkDiskSpace($directory = './')
  238. {
  239. $readable = @is_readable($directory);
  240. if ($readable) {
  241. $disk = $this->checkDisk($directory);
  242. $diskLevels = [
  243. 'warn' => 1000000000,
  244. 'error' => 100000000
  245. ];
  246. if ($disk['free'] <= $diskLevels['error']) {
  247. die($this->showHTML('Low Disk Space', 'You are dangerously low on disk space.<br/>There is only ' . $disk['free']['human_readable'] . ' remaining.<br/><b>Percent Used = ' . $disk['used']['percent_used'] . '%</b>'));
  248. } elseif ($disk['free'] <= $diskLevels['warn']) {
  249. $this->warnings[] = 'You are low on disk space. There is only ' . $disk['free']['human_readable'] . ' remaining.';
  250. }
  251. }
  252. return true;
  253. }
  254. public function getFreeSpace($directory = './')
  255. {
  256. $disk = disk_free_space($directory);
  257. return [
  258. 'raw' => $disk,
  259. 'human_readable' => $this->human_filesize($disk, 0)
  260. ];
  261. }
  262. public function getDiskSpace($directory = './')
  263. {
  264. $disk = disk_total_space($directory);
  265. return [
  266. 'raw' => $disk,
  267. 'human_readable' => $this->human_filesize($disk, 0)
  268. ];
  269. }
  270. public function getUsedSpace($directory = './')
  271. {
  272. $diskFree = $this->getFreeSpace($directory);
  273. $diskTotal = $this->getDiskSpace($directory);
  274. $diskUsed = $diskTotal['raw'] - $diskFree['raw'];
  275. $percentUsed = ($diskUsed / $diskTotal['raw']) * 100;
  276. $percentFree = 100 - $percentUsed;
  277. return [
  278. 'raw' => $diskUsed,
  279. 'human_readable' => $this->human_filesize($diskUsed, 0),
  280. 'percent_used' => round($percentUsed),
  281. 'percent_free' => round($percentFree)
  282. ];
  283. }
  284. public function checkDisk($directory = './')
  285. {
  286. $readable = @is_readable($directory);
  287. if ($readable) {
  288. return [
  289. 'free' => $this->getFreeSpace($directory),
  290. 'used' => $this->getUsedSpace($directory),
  291. 'total' => $this->getDiskSpace($directory),
  292. ];
  293. } else {
  294. return [
  295. 'free' => 'error accessing path',
  296. 'used' => 'error accessing path',
  297. 'total' => 'error accessing path',
  298. ];
  299. }
  300. }
  301. public function errorCodes($error = 000)
  302. {
  303. $errorCodes = [
  304. 400 => [
  305. 'type' => 'Bad Request',
  306. 'description' => 'The request was incorrect'
  307. ],
  308. 401 => [
  309. 'type' => 'Unauthorized ',
  310. 'description' => 'You are not authorized to view this page'
  311. ],
  312. 402 => [
  313. 'type' => 'Payment Required',
  314. 'description' => 'Payment required before you can view this page'
  315. ],
  316. 403 => [
  317. 'type' => 'Forbidden',
  318. 'description' => 'You are forbidden to view this page'
  319. ],
  320. 404 => [
  321. 'type' => 'Not Found',
  322. 'description' => 'The requested resource was not found'
  323. ],
  324. 405 => [
  325. 'type' => 'Method Not Allowed',
  326. 'description' => 'The requested method is not allowed'
  327. ],
  328. 406 => [
  329. 'type' => 'Not Acceptable',
  330. 'description' => 'There was an issue with the requests Headers'
  331. ],
  332. 407 => [
  333. 'type' => 'Proxy Authentication Required',
  334. 'description' => 'Authentication is required and was not passed'
  335. ],
  336. 408 => [
  337. 'type' => 'Request Time-out',
  338. 'description' => 'The request has timed out'
  339. ],
  340. 409 => [
  341. 'type' => 'Conflict',
  342. 'description' => 'An error has occurred'
  343. ],
  344. 410 => [
  345. 'type' => 'Gone',
  346. 'description' => 'The requested resource is no longer available and has been permanently removed'
  347. ],
  348. 411 => [
  349. 'type' => 'Length Required',
  350. 'description' => 'The request can not be processed without a "Content-Length" header field'
  351. ],
  352. 412 => [
  353. 'type' => 'Precondition Failed',
  354. 'description' => ' A header needed was not found'
  355. ],
  356. 413 => [
  357. 'type' => 'Request Entity Too Large',
  358. 'description' => 'The query was too large to be processed by the server'
  359. ],
  360. 414 => [
  361. 'type' => 'Request-URI Too Long',
  362. 'description' => 'The URI of the request was too long'
  363. ],
  364. 415 => [
  365. 'type' => 'Unsupported Media Type',
  366. 'description' => 'The contents of the request has been submitted with invalid or out of defined media type'
  367. ],
  368. 416 => [
  369. 'type' => 'Requested range not satisfiable',
  370. 'description' => 'The requested resource was part of an invalid or is not on the server'
  371. ],
  372. 417 => [
  373. 'type' => 'Expectation Failed',
  374. 'description' => 'Expected Header was not found'
  375. ],
  376. 444 => [
  377. 'type' => 'No Response',
  378. 'description' => 'Nothing was returned from server'
  379. ],
  380. 500 => [
  381. 'type' => 'Internal Server Error',
  382. 'description' => 'An unexpected server error'
  383. ],
  384. 501 => [
  385. 'type' => 'Not Implemented',
  386. 'description' => 'The functionality to process the request is not available from this server'
  387. ],
  388. 502 => [
  389. 'type' => 'Bad Gateway',
  390. 'description' => 'The server could not fulfill its function as a gateway or proxy'
  391. ],
  392. 503 => [
  393. 'type' => 'Service Unavailable',
  394. 'description' => 'The server is temporarily unavailable, due to overloading or maintenance'
  395. ],
  396. 504 => [
  397. 'type' => 'Gateway Time-out',
  398. 'description' => 'The server could not fulfill its function as a gateway or proxy'
  399. ],
  400. 505 => [
  401. 'type' => 'HTTP version not supported',
  402. 'description' => 'The used version of HTTP is not supported by the server or rejected'
  403. ],
  404. 507 => [
  405. 'type' => 'Insufficient Storage',
  406. 'description' => 'The request could not be processed because the server disk space it currently is not sufficient'
  407. ],
  408. 509 => [
  409. 'type' => 'Bandwidth Limit Exceeded',
  410. 'description' => 'The request was rejected, because otherwise the bandwidth would be exceeded'
  411. ],
  412. 510 => [
  413. 'type' => 'Not Extended',
  414. 'description' => 'The request does not contain all information that is waiting for the requested server extension imperative'
  415. ],
  416. 000 => [
  417. 'type' => 'Unexpected Error',
  418. 'description' => 'An unexpected error occurred'
  419. ],
  420. ];
  421. return (isset($errorCodes[$error])) ? $errorCodes[$error] : $errorCodes[000];
  422. }
  423. public function showTopBarHamburger()
  424. {
  425. if ($this->config['allowCollapsableSideMenu']) {
  426. if ($this->config['sideMenuCollapsed']) {
  427. return '<a class="toggle-side-menu" href="javascript:void(0)"><i class="ti-menu fa-fw"></i></a>';
  428. } else {
  429. return '<a class="toggle-side-menu hidden" href="javascript:void(0)"><i class="ti-menu fa-fw"></i></a>';
  430. }
  431. }
  432. return '';
  433. }
  434. public function showSideBarHamburger()
  435. {
  436. if ($this->config['allowCollapsableSideMenu']) {
  437. if (!$this->config['sideMenuCollapsed']) {
  438. return '<i class="hidden-xs ti-shift-left mouse"></i>';
  439. }
  440. }
  441. return '<i class="ti-menu hidden-xs"></i>';
  442. }
  443. public function showSideBarText()
  444. {
  445. if ($this->config['allowCollapsableSideMenu']) {
  446. if (!$this->config['sideMenuCollapsed']) {
  447. return '<span class="hide-menu hidden-xs" lang="en">Hide Menu</span>';
  448. }
  449. }
  450. return '<span class="hide-menu hidden-xs" lang="en">Navigation</span>';
  451. }
  452. public function auth()
  453. {
  454. if ($this->hasDB()) {
  455. if (isset($_GET['type'])) {
  456. switch (strtolower($_GET['type'])) {
  457. case 'whitelist':
  458. case 'white':
  459. case 'w':
  460. case 'wl':
  461. case 'allow':
  462. $_GET['whitelist'] = $_GET['ips'] ?? false;
  463. break;
  464. case 'blacklist':
  465. case 'black':
  466. case 'b':
  467. case 'bl':
  468. case 'deny':
  469. $_GET['blacklist'] = $_GET['ips'] ?? false;
  470. break;
  471. default:
  472. $this->setAPIResponse('error', $_GET['type'] . ' is not a valid type', 401);
  473. return true;
  474. }
  475. }
  476. $whitelist = $_GET['whitelist'] ?? false;
  477. $blacklist = $_GET['blacklist'] ?? false;
  478. $group = 0;
  479. $groupParam = ($_GET['group']) ?? 0;
  480. $redirect = false;
  481. if (isset($groupParam)) {
  482. if (is_numeric($groupParam)) {
  483. $group = (int)$groupParam;
  484. } else {
  485. $group = $this->getTabGroupByTabName($groupParam);
  486. }
  487. }
  488. $currentIP = $this->userIP();
  489. $unlocked = !($this->user['locked'] == '1');
  490. if (isset($this->user)) {
  491. $currentUser = $this->user['username'];
  492. $currentGroup = $this->user['groupID'];
  493. $currentEmail = $this->user['email'];
  494. } else {
  495. $currentUser = 'Guest';
  496. $currentGroup = $this->getUserLevel();
  497. $currentEmail = 'guest@guest.com';
  498. }
  499. $userInfo = [
  500. "user" => $currentUser,
  501. "group" => $currentGroup,
  502. "email" => $currentEmail,
  503. "user_ip" => $currentIP,
  504. "requested_group" => $group
  505. ];
  506. $responseMessage = 'User is not Authorized or User is locked';
  507. if ($whitelist) {
  508. if (in_array($currentIP, $this->arrayIP($whitelist))) {
  509. $responseMessage = 'User is whitelisted';
  510. $this->setAPIResponse('success', $responseMessage, 200, $userInfo);
  511. return true;
  512. }
  513. }
  514. if ($blacklist) {
  515. if (in_array($currentIP, $this->arrayIP($blacklist))) {
  516. $responseMessage = 'User is blacklisted';
  517. $this->setAPIResponse('error', $responseMessage, 401, $userInfo);
  518. return true;
  519. }
  520. }
  521. if ($group !== null) {
  522. if ((isset($_SERVER['HTTP_X_FORWARDED_SERVER']) && $_SERVER['HTTP_X_FORWARDED_SERVER'] == 'traefik') || $this->config['traefikAuthEnable']) {
  523. $return = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) && isset($_SERVER['HTTP_X_FORWARDED_URI']) && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) ? '?return=' . $_SERVER['HTTP_X_FORWARDED_PROTO'] . '://' . $_SERVER['HTTP_X_FORWARDED_HOST'] . $_SERVER['HTTP_X_FORWARDED_URI'] : '';
  524. $redirectDomain = ($this->config['traefikDomainOverride'] !== '') ? $this->config['traefikDomainOverride'] : $this->getServerPath();
  525. $redirect = 'Location: ' . $redirectDomain . $return;
  526. }
  527. if ($this->qualifyRequest($group) && $unlocked) {
  528. header("X-Organizr-User: $currentUser");
  529. header("X-Organizr-Email: $currentEmail");
  530. header("X-Organizr-Group: $currentGroup");
  531. $responseMessage = 'User is authorized';
  532. $this->setAPIResponse('success', $responseMessage, 200, $userInfo);
  533. } else {
  534. if (!$redirect) {
  535. $this->setAPIResponse('error', $responseMessage, 401, $userInfo);
  536. } else {
  537. exit(http_response_code(401) . header($redirect));
  538. }
  539. }
  540. } else {
  541. $this->setAPIResponse('error', 'Missing info', 401);
  542. }
  543. }
  544. return true;
  545. }
  546. public function getIpInfo($ip = null)
  547. {
  548. if (!$ip) {
  549. $this->setResponse(422, 'No IP Address supplied');
  550. return false;
  551. }
  552. try {
  553. $options = array('verify' => false);
  554. $response = Requests::get('https://ipinfo.io/' . $ip . '/?token=ddd0c072ad5021', array(), $options);
  555. if ($response->success) {
  556. $api = json_decode($response->body, true);
  557. $this->setResponse(200, null, $api);
  558. return true;
  559. } else {
  560. $this->setResponse(500, 'An error occurred', null);
  561. }
  562. } catch (Requests_Exception $e) {
  563. $this->setResponse(500, 'An error occurred', $e->getMessage());
  564. }
  565. return false;
  566. }
  567. public function setAPIResponse($result = null, $message = null, $responseCode = null, $data = null)
  568. {
  569. if ($result) {
  570. $GLOBALS['api']['response']['result'] = $result;
  571. }
  572. if ($message) {
  573. $GLOBALS['api']['response']['message'] = $message;
  574. }
  575. if ($responseCode) {
  576. $GLOBALS['responseCode'] = $responseCode;
  577. }
  578. if ($data) {
  579. $GLOBALS['api']['response']['data'] = $data;
  580. }
  581. }
  582. public function setResponse(int $responseCode = 200, string $message = null, $data = null)
  583. {
  584. switch ($responseCode) {
  585. case 200:
  586. case 201:
  587. case 204:
  588. $result = 'success';
  589. break;
  590. default:
  591. $result = 'error';
  592. break;
  593. }
  594. $GLOBALS['api']['response']['result'] = $result;
  595. if ($message) {
  596. $GLOBALS['api']['response']['message'] = $message;
  597. }
  598. if ($responseCode) {
  599. $GLOBALS['responseCode'] = $responseCode;
  600. }
  601. if ($data) {
  602. $GLOBALS['api']['response']['data'] = $data;
  603. }
  604. }
  605. public function checkRoute($request)
  606. {
  607. $route = '/api/v2/' . explode('api/v2/', $request->getUri()->getPath())[1];
  608. $method = $request->getMethod();
  609. $data = $this->apiData($request);
  610. if (!in_array($route, $GLOBALS['bypass'])) {
  611. if ($this->isApprovedRequest($method, $data) === false) {
  612. $this->setAPIResponse('error', 'Not authorized for current Route: ' . $route, 401);
  613. $this->writeLog('success', 'Killed Attack From [' . (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'No Referer') . ']', $this->user['username']);
  614. return false;
  615. }
  616. }
  617. return true;
  618. }
  619. public function apiData($request)
  620. {
  621. switch ($request->getMethod()) {
  622. case 'POST':
  623. if (stripos($request->getHeaderLine('Content-Type'), 'application/json') !== false) {
  624. return json_decode(file_get_contents('php://input', 'r'), true);
  625. } else {
  626. return $request->getParsedBody();
  627. }
  628. default:
  629. if (stripos($request->getHeaderLine('Content-Type'), 'application/json') !== false) {
  630. return json_decode(file_get_contents('php://input', 'r'), true);
  631. } else {
  632. return null;
  633. }
  634. }
  635. }
  636. public function getPlugins()
  637. {
  638. if ($this->hasDB()) {
  639. $pluginList = [];
  640. foreach ($GLOBALS['plugins'] as $plugin) {
  641. foreach ($plugin as $key => $value) {
  642. if (strpos($value['license'], $this->config['license']) !== false) {
  643. $plugin[$key]['enabled'] = $this->config[$value['configPrefix'] . '-enabled'];
  644. $pluginList[$key] = $plugin[$key];
  645. }
  646. }
  647. }
  648. asort($pluginList);
  649. return $pluginList;
  650. }
  651. return false;
  652. }
  653. public function refreshCookieName()
  654. {
  655. $this->cookieName = $this->config['uuid'] !== '' ? 'organizr_token_' . $this->config['uuid'] : 'organizr_token_temp';
  656. }
  657. public function favIcons($rootPath = '')
  658. {
  659. $favicon = '
  660. <link rel="apple-touch-icon" sizes="180x180" href="' . $rootPath . 'plugins/images/favicon/apple-touch-icon.png">
  661. <link rel="icon" type="image/png" sizes="32x32" href="' . $rootPath . 'plugins/images/favicon/favicon-32x32.png">
  662. <link rel="icon" type="image/png" sizes="16x16" href="' . $rootPath . 'plugins/images/favicon/favicon-16x16.png">
  663. <link rel="manifest" href="' . $rootPath . 'plugins/images/favicon/site.webmanifest" crossorigin="use-credentials">
  664. <link rel="mask-icon" href="' . $rootPath . 'plugins/images/favicon/safari-pinned-tab.svg" color="#5bbad5">
  665. <link rel="shortcut icon" href="' . $rootPath . 'plugins/images/favicon/favicon.ico">
  666. <meta name="msapplication-TileColor" content="#da532c">
  667. <meta name="msapplication-TileImage" content="' . $rootPath . 'plugins/images/favicon/mstile-144x144.png">
  668. <meta name="msapplication-config" content="' . $rootPath . 'plugins/images/favicon/browserconfig.xml">
  669. <meta name="theme-color" content="#ffffff">
  670. ';
  671. if ($this->config['favIcon'] !== '' && $rootPath !== '') {
  672. $this->config['favIcon'] = str_replace('plugins/images/faviconCustom', $rootPath . 'plugins/images/faviconCustom', $this->config['favIcon']);
  673. }
  674. return ($this->config['favIcon'] == '') ? $favicon : $this->config['favIcon'];
  675. }
  676. public function pluginGlobalList()
  677. {
  678. $pluginSearch = '-enabled';
  679. $pluginInclude = '-include';
  680. $plugins = array_filter($this->config, function ($k) use ($pluginSearch) {
  681. return stripos($k, $pluginSearch) !== false;
  682. }, ARRAY_FILTER_USE_KEY);
  683. $plugins['includes'] = array_filter($this->config, function ($k) use ($pluginInclude) {
  684. return stripos($k, $pluginInclude) !== false;
  685. }, ARRAY_FILTER_USE_KEY);
  686. return $plugins;
  687. }
  688. public function googleTracking()
  689. {
  690. if ($this->config['gaTrackingID'] !== '') {
  691. return '
  692. <script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
  693. <script async src="https://www.googletagmanager.com/gtag/js?id=' . $this->config['gaTrackingID'] . '"></script>
  694. <script>
  695. window.dataLayer = window.dataLayer || [];
  696. function gtag(){dataLayer.push(arguments);}
  697. gtag("js", new Date());
  698. gtag("config","' . $this->config['gaTrackingID'] . '");
  699. </script>
  700. ';
  701. }
  702. return null;
  703. }
  704. public function matchBrackets($text, $brackets = 's')
  705. {
  706. switch ($brackets) {
  707. case 's':
  708. case 'square':
  709. $pattern = '#\[(.*?)\]#';
  710. break;
  711. case 'c':
  712. case 'curly':
  713. $pattern = '#\((.*?)\)#';
  714. break;
  715. default:
  716. return null;
  717. }
  718. preg_match($pattern, $text, $match);
  719. return $match[1];
  720. }
  721. public function languagePacks($encode = false)
  722. {
  723. $files = array();
  724. foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'langpack' . DIRECTORY_SEPARATOR . "*.json") as $filename) {
  725. if (strpos(basename($filename), '[') !== false) {
  726. $explode = explode('[', basename($filename));
  727. $files[] = array(
  728. 'filename' => basename($filename),
  729. 'code' => $explode[0],
  730. 'language' => $this->matchBrackets(basename($filename))
  731. );
  732. }
  733. }
  734. usort($files, function ($a, $b) {
  735. return $a['language'] <=> $b['language'];
  736. });
  737. return ($encode) ? json_encode($files) : $files;
  738. }
  739. public function getRootPath()
  740. {
  741. $count = (count(explode('/', $_SERVER['REQUEST_URI']))) - 2;
  742. $rootPath = '';
  743. $rootPath .= str_repeat('../', $count);
  744. return $rootPath;
  745. }
  746. public function setTheme($theme = null, $rootPath = '')
  747. {
  748. $theme = $theme ?? $this->config['theme'];
  749. return '<link id="theme" href="' . $rootPath . 'css/themes/' . $theme . '.css?v=' . $this->fileHash . '" rel="stylesheet">';
  750. }
  751. public function pluginFiles($type, $settings = false, $rootPath = '')
  752. {
  753. $files = '';
  754. $folder = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'plugins';
  755. $directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
  756. $iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
  757. switch ($type) {
  758. case 'js':
  759. foreach ($iteratorIterator as $info) {
  760. if (pathinfo($info->getPathname(), PATHINFO_EXTENSION) == 'js') {
  761. $pluginEnabled = false;
  762. $keyOriginal = strtoupper(basename(dirname($info->getPathname())));
  763. $key = str_replace('-SETTINGS', '', $keyOriginal);
  764. $continue = false;
  765. if ($settings) {
  766. if ($info->getFilename() == 'settings.js') {
  767. $continue = true;
  768. }
  769. } else {
  770. if ($info->getFilename() !== 'settings.js') {
  771. $continue = true;
  772. }
  773. }
  774. switch ($key) {
  775. case 'PHP-MAILER':
  776. $key = 'PHPMAILER';
  777. break;
  778. case 'NGXC':
  779. $key = 'ngxc';
  780. break;
  781. default:
  782. $key = $key;
  783. }
  784. if (isset($this->config[$key . '-enabled'])) {
  785. if ($this->config[$key . '-enabled']) {
  786. $pluginEnabled = true;
  787. }
  788. }
  789. if ($pluginEnabled || $settings) {
  790. if ($continue) {
  791. $files .= '<script src="' . $rootPath . 'api/plugins/' . basename(dirname($info->getPathname())) . '/' . basename($info->getFilename()) . '?v=' . $this->fileHash . '" defer="true"></script>';
  792. }
  793. }
  794. }
  795. }
  796. break;
  797. case 'css':
  798. foreach ($iteratorIterator as $info) {
  799. if (pathinfo($info->getPathname(), PATHINFO_EXTENSION) == 'css') {
  800. $files .= '<link href="' . $rootPath . 'api/plugins/' . basename(dirname($info->getPathname())) . '/' . basename($info->getFilename()) . '?v=' . $this->fileHash . '" rel="stylesheet">';
  801. }
  802. }
  803. break;
  804. default:
  805. break;
  806. }
  807. return $files;
  808. }
  809. public function formKey($script = true)
  810. {
  811. if (isset($this->config['organizrHash'])) {
  812. if ($this->config['organizrHash'] !== '') {
  813. $hash = password_hash(substr($this->config['organizrHash'], 2, 10), PASSWORD_BCRYPT);
  814. return ($script) ? '<script>local("s","formKey","' . $hash . '");</script>' : $hash;
  815. }
  816. }
  817. }
  818. private function checkPHP()
  819. {
  820. if (!(version_compare(PHP_VERSION, $this->minimumPHP) >= 0)) {
  821. die($this->showHTML('PHP Version', 'Organizr needs PHP Version: ' . $this->minimumPHP . '<br/> You have PHP Version: ' . PHP_VERSION));
  822. }
  823. }
  824. private function checkWritableDB()
  825. {
  826. if ($this->hasDB()) {
  827. if (isset($this->config['dbLocation']) && isset($this->config['dbName'])) {
  828. $db = is_writable($this->config['dbLocation'] . $this->config['dbName']);
  829. if (!$db) {
  830. die($this->showHTML('Organizr DB is not writable!', 'Please check permissions and/or disk space'));
  831. }
  832. } else {
  833. die($this->showHTML('Config File Malformed', 'dbLocation and/or dbName is not listed in config.php'));
  834. }
  835. }
  836. }
  837. public function upgradeCheck()
  838. {
  839. if ($this->hasDB()) {
  840. $tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
  841. $updateComplete = $this->config['dbLocation'] . 'completed.txt';
  842. $cleanup = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR;
  843. if (file_exists($updateComplete)) {
  844. @unlink($updateComplete);
  845. @$this->rrmdir($cleanup);
  846. }
  847. if (file_exists($tempLock)) {
  848. die($this->showHTML('Upgrading', 'Please wait...'));
  849. }
  850. $updateDB = false;
  851. $updateSuccess = true;
  852. $compare = new Composer\Semver\Comparator;
  853. $oldVer = $this->config['configVersion'];
  854. // Upgrade check start for version below
  855. $versionCheck = '2.0.0-beta-200';
  856. if ($compare->lessThan($oldVer, $versionCheck)) {
  857. $updateDB = true;
  858. $oldVer = $versionCheck;
  859. }
  860. // End Upgrade check start for version above
  861. // Upgrade check start for version below
  862. $versionCheck = '2.0.0-beta-500';
  863. if ($compare->lessThan($oldVer, $versionCheck)) {
  864. $updateDB = true;
  865. $oldVer = $versionCheck;
  866. }
  867. // End Upgrade check start for version above
  868. // Upgrade check start for version below
  869. $versionCheck = '2.0.0-beta-800';
  870. if ($compare->lessThan($oldVer, $versionCheck)) {
  871. $updateDB = true;
  872. $oldVer = $versionCheck;
  873. }
  874. // End Upgrade check start for version above
  875. // Upgrade check start for version below
  876. $versionCheck = '2.1.0';
  877. if ($compare->lessThan($oldVer, $versionCheck)) {
  878. $updateDB = false;
  879. $oldVer = $versionCheck;
  880. $this->upgradeToVersion($versionCheck);
  881. }
  882. // End Upgrade check start for version above
  883. // Upgrade check start for version below
  884. $versionCheck = '2.1.400';
  885. if ($compare->lessThan($oldVer, $versionCheck)) {
  886. $updateDB = false;
  887. $oldVer = $versionCheck;
  888. $this->upgradeToVersion($versionCheck);
  889. }
  890. // End Upgrade check start for version above
  891. // Upgrade check start for version below
  892. $versionCheck = '2.1.525';
  893. if ($compare->lessThan($oldVer, $versionCheck)) {
  894. $updateDB = false;
  895. $oldVer = $versionCheck;
  896. $this->upgradeToVersion($versionCheck);
  897. }
  898. // End Upgrade check start for version above
  899. if ($updateDB == true) {
  900. //return 'Upgraded Needed - Current Version '.$oldVer.' - New Version: '.$versionCheck;
  901. // Upgrade database to latest version
  902. $updateSuccess = $this->updateDB($oldVer);
  903. }
  904. // Update config.php version if different to the installed version
  905. if ($updateSuccess && $this->version !== $this->config['configVersion']) {
  906. $this->updateConfig(array('apply_CONFIG_VERSION' => $this->version));
  907. $this->debug('Updated config version to ' . $this->version);
  908. }
  909. if ($updateSuccess == false) {
  910. die($this->showHTML('Database update failed', 'Please manually check logs and fix - Then reload this page'));
  911. }
  912. return true;
  913. }
  914. }
  915. public function updateDB($oldVerNum = false)
  916. {
  917. $tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
  918. if (!file_exists($tempLock)) {
  919. touch($tempLock);
  920. $migrationDB = 'tempMigration.db';
  921. $pathDigest = pathinfo($this->config['dbLocation'] . $this->config['dbName']);
  922. if (file_exists($this->config['dbLocation'] . $migrationDB)) {
  923. unlink($this->config['dbLocation'] . $migrationDB);
  924. }
  925. // Create Temp DB First
  926. $this->connectOtherDB();
  927. $backupDB = $pathDigest['dirname'] . '/' . $pathDigest['filename'] . '[' . date('Y-m-d_H-i-s') . ']' . ($oldVerNum ? '[' . $oldVerNum . ']' : '') . '.bak.db';
  928. copy($this->config['dbLocation'] . $this->config['dbName'], $backupDB);
  929. $success = $this->createDB($this->config['dbLocation'], true);
  930. if ($success) {
  931. $response = [
  932. array(
  933. 'function' => 'fetchAll',
  934. 'query' => array(
  935. 'SELECT name FROM sqlite_master WHERE type="table"'
  936. )
  937. ),
  938. ];
  939. $tables = $this->processQueries($response);
  940. foreach ($tables as $table) {
  941. $response = [
  942. array(
  943. 'function' => 'fetchAll',
  944. 'query' => array(
  945. 'SELECT * FROM ' . $table['name']
  946. )
  947. ),
  948. ];
  949. $data = $this->processQueries($response);
  950. $this->writeLog('success', 'Update Function - Grabbed Table data for Table: ' . $table['name'], 'Database');
  951. foreach ($data as $row) {
  952. $response = [
  953. array(
  954. 'function' => 'query',
  955. 'query' => array(
  956. 'INSERT into ' . $table['name'],
  957. $row
  958. )
  959. ),
  960. ];
  961. $this->processQueries($response, true);
  962. }
  963. $this->writeLog('success', 'Update Function - Wrote Table data for Table: ' . $table['name'], 'Database');
  964. }
  965. $this->writeLog('success', 'Update Function - All Table data converted - Starting Movement', 'Database');
  966. $this->db->disconnect();
  967. $this->otherDb->disconnect();
  968. // Remove Current Database
  969. if (file_exists($this->config['dbLocation'] . $migrationDB)) {
  970. $oldFileSize = filesize($this->config['dbLocation'] . $this->config['dbName']);
  971. $newFileSize = filesize($this->config['dbLocation'] . $migrationDB);
  972. if ($newFileSize > 0) {
  973. $this->writeLog('success', 'Update Function - Table Size of new DB ok..', 'Database');
  974. @unlink($this->config['dbLocation'] . $this->config['dbName']);
  975. copy($this->config['dbLocation'] . $migrationDB, $this->config['dbLocation'] . $this->config['dbName']);
  976. @unlink($this->config['dbLocation'] . $migrationDB);
  977. $this->writeLog('success', 'Update Function - Migrated Old Info to new Database', 'Database');
  978. @unlink($tempLock);
  979. return true;
  980. } else {
  981. $this->writeLog('error', 'Update Function - Filesize is zero', 'Database');
  982. }
  983. } else {
  984. $this->writeLog('error', 'Update Function - Migration DB does not exist', 'Database');
  985. }
  986. @unlink($tempLock);
  987. return false;
  988. } else {
  989. $this->writeLog('error', 'Update Function - Could not create migration DB', 'Database');
  990. }
  991. @unlink($tempLock);
  992. return false;
  993. }
  994. return false;
  995. }
  996. // Create config file in the return syntax
  997. public function createConfig($array, $path = null, $nest = 0)
  998. {
  999. $path = ($path) ? $path : $this->userConfigPath;
  1000. // Define Initial Value
  1001. $output = array();
  1002. // Sort Items
  1003. ksort($array);
  1004. // Update the current config version
  1005. if (!$nest) {
  1006. // Inject Current Version
  1007. $output[] = "\t'configVersion' => '" . (isset($array['apply_CONFIG_VERSION']) ? $array['apply_CONFIG_VERSION'] : $this->version) . "'";
  1008. }
  1009. unset($array['configVersion']);
  1010. unset($array['apply_CONFIG_VERSION']);
  1011. // Process Settings
  1012. foreach ($array as $k => $v) {
  1013. $allowCommit = true;
  1014. $item = '';
  1015. switch (gettype($v)) {
  1016. case 'boolean':
  1017. $item = ($v ? 'true' : 'false');
  1018. break;
  1019. case 'integer':
  1020. case 'double':
  1021. case 'NULL':
  1022. $item = $v;
  1023. break;
  1024. case 'string':
  1025. $item = "'" . str_replace(array('\\', "'"), array('\\\\', "\'"), $v) . "'";
  1026. break;
  1027. case 'array':
  1028. $item = $this->createConfig($v, false, $nest + 1);
  1029. break;
  1030. default:
  1031. $allowCommit = false;
  1032. }
  1033. if ($allowCommit) {
  1034. $output[] = str_repeat("\t", $nest + 1) . "'$k' => $item";
  1035. }
  1036. }
  1037. // Build output
  1038. $output = (!$nest ? "<?php\nreturn " : '') . "[\n" . implode(",\n", $output) . "\n" . str_repeat("\t", $nest) . ']' . (!$nest ? ';' : '');
  1039. if (!$nest && $path) {
  1040. $pathDigest = pathinfo($path);
  1041. @mkdir($pathDigest['dirname'], 0770, true);
  1042. if (file_exists($path)) {
  1043. rename($path, $pathDigest['dirname'] . '/' . $pathDigest['filename'] . '.bak.php');
  1044. }
  1045. $file = fopen($path, 'w');
  1046. fwrite($file, $output);
  1047. fclose($file);
  1048. if (file_exists($path)) {
  1049. return true;
  1050. }
  1051. return false;
  1052. } else {
  1053. return $output;
  1054. }
  1055. }
  1056. // Commit new values to the configuration
  1057. public function updateConfig($new, $current = false)
  1058. {
  1059. // Get config if not supplied
  1060. if ($current === false) {
  1061. //$current = $this->loadConfig();
  1062. $current = $this->config;
  1063. } elseif (is_string($current) && is_file($current)) {
  1064. $current = $this->loadConfig($current);
  1065. }
  1066. // Inject Parts
  1067. foreach ($new as $k => $v) {
  1068. $current[$k] = $v;
  1069. $this->config[$k] = $v;
  1070. }
  1071. // Return Create
  1072. return $this->createConfig($current);
  1073. }
  1074. public function removeConfigItem($new, $current = false)
  1075. {
  1076. // Get config if not supplied
  1077. if ($current === false) {
  1078. $current = $this->config;
  1079. } elseif (is_string($current) && is_file($current)) {
  1080. $current = $this->loadConfig($current);
  1081. }
  1082. // Inject Parts
  1083. foreach ($new as $k) {
  1084. if (isset($current[$k])) {
  1085. $current['deletedConfigItems'][$k] = $current[$k];
  1086. $this->config['deletedConfigItems'][$k] = $current[$k];
  1087. }
  1088. unset($current[$k]);
  1089. unset($this->config[$k]);
  1090. }
  1091. // Return Create
  1092. return $this->createConfig($current);
  1093. }
  1094. public function loadConfig($path = null)
  1095. {
  1096. $path = ($path) ? $path : $this->userConfigPath;
  1097. if (!is_file($path)) {
  1098. return null;
  1099. } else {
  1100. return (array)call_user_func(function () use ($path) {
  1101. return include($path);
  1102. });
  1103. }
  1104. }
  1105. public function fillDefaultConfig($array)
  1106. {
  1107. $path = $this->defaultConfigPath;
  1108. if (is_string($path)) {
  1109. $loadedDefaults = $this->loadConfig($path);
  1110. } else {
  1111. $loadedDefaults = $path;
  1112. }
  1113. // Include all plugin config files
  1114. $folder = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'plugins';
  1115. $directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
  1116. $iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
  1117. foreach ($iteratorIterator as $info) {
  1118. if ($info->getFilename() == 'config.php') {
  1119. $loadedDefaults = array_merge($loadedDefaults, $this->loadConfig($info->getPathname()));
  1120. }
  1121. }
  1122. return (is_array($loadedDefaults) ? $this->fillDefaultConfig_recurse($array, $loadedDefaults) : false);
  1123. }
  1124. public function fillDefaultConfig_recurse($current, $defaults)
  1125. {
  1126. foreach ($defaults as $k => $v) {
  1127. if (!isset($current[$k])) {
  1128. $current[$k] = $v;
  1129. } elseif (is_array($current[$k]) && is_array($v)) {
  1130. $current[$k] = $this->fillDefaultConfig_recurse($current[$k], $v);
  1131. }
  1132. }
  1133. return $current;
  1134. }
  1135. public function config($tries = 1)
  1136. {
  1137. // Load config or default
  1138. if (file_exists($this->userConfigPath)) {
  1139. $config = $this->fillDefaultConfig($this->loadConfig($this->userConfigPath));
  1140. } else {
  1141. $config = $this->fillDefaultConfig($this->loadConfig($this->defaultConfigPath));
  1142. }
  1143. if ((!is_array($config) || !file_exists($this->userConfigPath)) && $tries < 5) {
  1144. $tries++;
  1145. return $this->config($tries);
  1146. }
  1147. return $config;
  1148. }
  1149. public function combineConfig($array)
  1150. {
  1151. $this->config = array_merge($this->config, $array);
  1152. return $this->config;
  1153. }
  1154. public function status()
  1155. {
  1156. $status = array();
  1157. $dependenciesActive = array();
  1158. $dependenciesInactive = array();
  1159. $extensions = array("PDO_SQLITE", "PDO", "SQLITE3", "zip", "cURL", "openssl", "simplexml", "json", "session", "filter");
  1160. $functions = array("hash", "fopen", "fsockopen", "fwrite", "fclose", "readfile");
  1161. foreach ($extensions as $check) {
  1162. if (extension_loaded($check)) {
  1163. array_push($dependenciesActive, $check);
  1164. } else {
  1165. array_push($dependenciesInactive, $check);
  1166. }
  1167. }
  1168. foreach ($functions as $check) {
  1169. if (function_exists($check)) {
  1170. array_push($dependenciesActive, $check);
  1171. } else {
  1172. array_push($dependenciesInactive, $check);
  1173. }
  1174. }
  1175. if (!file_exists($this->userConfigPath)) {
  1176. $status['status'] = "wizard";//wizard - ok for test
  1177. }
  1178. if (count($dependenciesInactive) > 0 || !is_writable(dirname(__DIR__, 2)) || !(version_compare(PHP_VERSION, $this->minimumPHP) >= 0)) {
  1179. $status['status'] = "dependencies";
  1180. }
  1181. $status['status'] = ($status['status']) ?? "ok";
  1182. $status['writable'] = is_writable(dirname(__DIR__, 2)) ? 'yes' : 'no';
  1183. $status['minVersion'] = (version_compare(PHP_VERSION, $this->minimumPHP) >= 0) ? 'yes' : 'no';
  1184. $status['dependenciesActive'] = $dependenciesActive;
  1185. $status['dependenciesInactive'] = $dependenciesInactive;
  1186. $status['version'] = $this->version;
  1187. $status['os'] = $this->getOS();
  1188. $status['php'] = phpversion();
  1189. $status['php_user'] = get_current_user();
  1190. $status['userConfigPath'] = $this->userConfigPath;
  1191. return $status;
  1192. }
  1193. public function hasDB()
  1194. {
  1195. return (file_exists($this->userConfigPath)) ?? false;
  1196. }
  1197. public function hasCookie()
  1198. {
  1199. return ($_COOKIE[$this->cookieName]) ?? false;
  1200. }
  1201. public function getGuest()
  1202. {
  1203. $guest = array(
  1204. 'group' => 'Guest',
  1205. 'group_id' => 999,
  1206. 'image' => 'plugins/images/groups/guest.png'
  1207. );
  1208. $response = [
  1209. array(
  1210. 'function' => 'fetch',
  1211. 'query' => 'SELECT * FROM groups WHERE `group_id` = 999'
  1212. ),
  1213. ];
  1214. return $this->hasDB() ? $this->processQueries($response) : $guest;
  1215. }
  1216. public function getSchema()
  1217. {
  1218. $response = [
  1219. array(
  1220. 'function' => 'fetchAll',
  1221. 'query' => 'SELECT name, sql FROM sqlite_master WHERE type=\'table\' ORDER BY name'
  1222. ),
  1223. ];
  1224. return $this->hasDB() ? $this->processQueries($response) : 'Database not setup yet';
  1225. }
  1226. public function guestUser()
  1227. {
  1228. if ($this->hasDB()) {
  1229. if ($this->getUserLevel() !== 999) {
  1230. $guest = array(
  1231. "token" => null,
  1232. "tokenDate" => null,
  1233. "tokenExpire" => null,
  1234. "username" => "Organizr API",
  1235. "uid" => $this->guestHash(0, 5),
  1236. "group" => 'Admin',
  1237. "groupID" => 0,
  1238. "email" => null,
  1239. //"groupImage"=>getGuest()['image'],
  1240. "image" => $this->getGuest()['image'],
  1241. "userID" => null,
  1242. "loggedin" => false,
  1243. "locked" => false,
  1244. "tokenList" => null,
  1245. "authService" => null
  1246. );
  1247. }
  1248. }
  1249. $guest = $guest ?? array(
  1250. "token" => null,
  1251. "tokenDate" => null,
  1252. "tokenExpire" => null,
  1253. "username" => "Guest",
  1254. "uid" => $this->guestHash(0, 5),
  1255. "group" => $this->getGuest()['group'],
  1256. "groupID" => $this->getGuest()['group_id'],
  1257. "email" => null,
  1258. //"groupImage"=>getGuest()['image'],
  1259. "image" => $this->getGuest()['image'],
  1260. "userID" => null,
  1261. "loggedin" => false,
  1262. "locked" => false,
  1263. "tokenList" => null,
  1264. "authService" => null
  1265. );
  1266. return $guest;
  1267. }
  1268. public function getAllUserTokens($id)
  1269. {
  1270. $response = [
  1271. array(
  1272. 'function' => 'fetchAll',
  1273. 'query' => array(
  1274. 'SELECT * FROM `tokens` WHERE user_id = ? AND expires > ?',
  1275. [$id],
  1276. [$this->currentTime]
  1277. )
  1278. ),
  1279. ];
  1280. return $this->processQueries($response);
  1281. }
  1282. public function getUserById($id)
  1283. {
  1284. $response = [
  1285. array(
  1286. 'function' => 'fetch',
  1287. 'query' => array(
  1288. 'SELECT * FROM users WHERE id = ?',
  1289. $id
  1290. )
  1291. )
  1292. ];
  1293. return $this->processQueries($response);
  1294. }
  1295. public function getUserByEmail($email)
  1296. {
  1297. $response = [
  1298. array(
  1299. 'function' => 'fetch',
  1300. 'query' => array(
  1301. 'SELECT * FROM users WHERE email = ? COLLATE NOCASE',
  1302. $email
  1303. )
  1304. )
  1305. ];
  1306. return $this->processQueries($response);
  1307. }
  1308. protected function invalidToken($token)
  1309. {
  1310. if (isset($_COOKIE[$this->cookieName])) {
  1311. if ($token == $_COOKIE[$this->cookieName]) {
  1312. $this->coookie('delete', $this->cookieName);
  1313. $this->user = null;
  1314. $this->debug('Token was invalid - deleting cookie and user session');
  1315. }
  1316. }
  1317. }
  1318. public function validateToken($token, $api = false)
  1319. {
  1320. // Validate script
  1321. $userInfo = $this->jwtParse($token);
  1322. $validated = (bool)$userInfo;
  1323. if ($validated == true) {
  1324. $allTokens = $this->getAllUserTokens($userInfo['userID']);
  1325. $user = $this->getUserById($userInfo['userID']);
  1326. $tokenCheck = ($this->searchArray($allTokens, 'token', $token) !== false);
  1327. if (!$tokenCheck) {
  1328. $this->debug('Token failed check Token listing: ' . json_encode($allTokens) . ' User Id: ' . $userInfo['userID']);
  1329. $this->invalidToken($token);
  1330. if ($api) {
  1331. $this->setResponse(403, 'Token was not in approved list');
  1332. }
  1333. return false;
  1334. } else {
  1335. if ($api) {
  1336. $this->setResponse(200, 'Token is valid');
  1337. }
  1338. return array(
  1339. 'token' => $token,
  1340. 'tokenDate' => $userInfo['tokenDate'],
  1341. 'tokenExpire' => $userInfo['tokenExpire'],
  1342. 'username' => $user['username'] ?? $userInfo['username'],
  1343. 'uid' => $this->guestHash(0, 5),
  1344. 'group' => $user['group'] ?? $userInfo['group'],
  1345. 'groupID' => $user['group_id'] ?? $userInfo['groupID'],
  1346. 'email' => $user['email'] ?? $userInfo['email'],
  1347. 'image' => $user['image'] ?? $userInfo['image'],
  1348. 'userID' => $user['id'] ?? $userInfo['userID'],
  1349. 'loggedin' => true,
  1350. 'locked' => $user['locked'] ?? 0,
  1351. 'tokenList' => $allTokens,
  1352. 'authService' => (isset($user['auth_service'])) ? explode('::', $user['auth_service'])[0] : 'internal'
  1353. );
  1354. }
  1355. } else {
  1356. if ($api) {
  1357. $this->setResponse(403, 'Token was invalid');
  1358. }
  1359. $this->debug('Token was invalid');
  1360. $this->invalidToken($token);
  1361. }
  1362. if ($api) {
  1363. $this->setResponse(403, 'Token was invalid');
  1364. }
  1365. return false;
  1366. }
  1367. public function getUserFromToken($token)
  1368. {
  1369. // Validate script
  1370. $userInfo = $this->jwtParse($token);
  1371. $validated = (bool)$userInfo;
  1372. if ($validated == true) {
  1373. $user = $this->getUserById($userInfo['userID']);
  1374. $allTokens = $this->getAllUserTokens($userInfo['userID']);
  1375. return array(
  1376. 'token' => $token,
  1377. 'tokenDate' => $userInfo['tokenDate'],
  1378. 'tokenExpire' => $userInfo['tokenExpire'],
  1379. 'username' => $user['username'] ?? $userInfo['username'],
  1380. 'uid' => $this->guestHash(0, 5),
  1381. 'group' => $user['group'] ?? $userInfo['group'],
  1382. 'groupID' => $user['group_id'] ?? $userInfo['groupID'],
  1383. 'email' => $user['email'] ?? $userInfo['email'],
  1384. 'image' => $user['image'] ?? $userInfo['image'],
  1385. 'userID' => $user['id'] ?? $userInfo['userID'],
  1386. 'loggedin' => true,
  1387. 'locked' => $user['locked'] ?? 0,
  1388. 'tokenList' => $allTokens,
  1389. 'authService' => (isset($user['auth_service'])) ? explode('::', $user['auth_service'])[0] : 'internal'
  1390. );
  1391. }
  1392. return false;
  1393. }
  1394. public function defaultUserGroup()
  1395. {
  1396. $response = [
  1397. array(
  1398. 'function' => 'fetch',
  1399. 'query' => 'SELECT * FROM groups WHERE `default` = 1'
  1400. )
  1401. ];
  1402. return $this->processQueries($response);
  1403. }
  1404. public function getAllTabs()
  1405. {
  1406. $response = [
  1407. array(
  1408. 'function' => 'fetchAll',
  1409. 'query' => 'SELECT * FROM tabs ORDER BY `order` ASC',
  1410. 'key' => 'tabs'
  1411. ),
  1412. array(
  1413. 'function' => 'fetchAll',
  1414. 'query' => 'SELECT * FROM categories ORDER BY `order` ASC',
  1415. 'key' => 'categories'
  1416. ),
  1417. array(
  1418. 'function' => 'fetchAll',
  1419. 'query' => 'SELECT * FROM groups ORDER BY `group_id` ASC',
  1420. 'key' => 'groups'
  1421. ),
  1422. ];
  1423. $query = $this->processQueries($response);
  1424. $this->applyTabVariables($query['tabs']);
  1425. return $query;
  1426. }
  1427. public function applyTabVariables($tabs)
  1428. {
  1429. $variables = [
  1430. '{domain}' => $this->getServer(),
  1431. '{username}' => $this->user['username'],
  1432. '{username_lower}' => $this->user['username'],
  1433. '{email}' => $this->user['email'],
  1434. '{group}' => $this->user['group'],
  1435. '{group_id}' => $this->user['groupID'],
  1436. '{komga}' => $_COOKIE['komga_token'] ?? ''
  1437. ];
  1438. if (empty($tabs)) {
  1439. return $tabs;
  1440. }
  1441. foreach ($tabs as $id => $tab) {
  1442. $tabs[$id]['url'] = $this->userDefinedIdReplacementLink($tab['url'], $variables);
  1443. $tabs[$id]['url_local'] = $this->userDefinedIdReplacementLink($tab['url_local'], $variables);
  1444. }
  1445. return $tabs;
  1446. }
  1447. public function getUsers()
  1448. {
  1449. $response = [
  1450. array(
  1451. 'function' => 'fetchAll',
  1452. 'query' => 'SELECT * FROM users'
  1453. ),
  1454. array(
  1455. 'function' => 'fetchAll',
  1456. 'query' => 'SELECT * FROM groups ORDER BY group_id ASC'
  1457. ),
  1458. ];
  1459. return $this->processQueries($response);
  1460. }
  1461. public function usernameTaken($username, $email, $id = null)
  1462. {
  1463. if ($id) {
  1464. $response = [
  1465. array(
  1466. 'function' => 'fetch',
  1467. 'query' => array(
  1468. 'SELECT * FROM users WHERE `id` != ? AND (username = ? COLLATE NOCASE or email = ? COLLATE NOCASE)',
  1469. $id,
  1470. $username,
  1471. $email
  1472. )
  1473. ),
  1474. ];
  1475. } else {
  1476. $response = [
  1477. array(
  1478. 'function' => 'fetch',
  1479. 'query' => array(
  1480. 'SELECT * FROM users WHERE username = ? COLLATE NOCASE or email = ? COLLATE NOCASE',
  1481. [$username],
  1482. [$email]
  1483. )
  1484. ),
  1485. ];
  1486. }
  1487. return $this->processQueries($response);
  1488. }
  1489. public function cleanPageName($page)
  1490. {
  1491. return ($page) ? strtolower(str_replace(array('%20', ' ', '-', '_'), '_', $page)) : '';
  1492. }
  1493. public function cleanClassName($name)
  1494. {
  1495. return ($name) ? (str_replace(array('%20', ' ', '-', '_'), '-', $name)) : '';
  1496. }
  1497. public function reverseCleanClassName($name)
  1498. {
  1499. return ($name) ? (str_replace(array('%20', '-', '_'), ' ', strtolower($name))) : '';
  1500. }
  1501. public function getPageList()
  1502. {
  1503. return $GLOBALS['organizrPages'];
  1504. }
  1505. public function getPage($page)
  1506. {
  1507. if (!$page) {
  1508. $this->setAPIResponse('error', 'Page not setup', 409);
  1509. return null;
  1510. }
  1511. $pageFunction = 'get_page_' . $this->cleanPageName($page);
  1512. if (function_exists($pageFunction)) {
  1513. return $pageFunction($this);
  1514. } else {
  1515. $this->setAPIResponse('error', 'Page not setup', 409);
  1516. return null;
  1517. }
  1518. }
  1519. public function getUserLevel()
  1520. {
  1521. // Grab token
  1522. $requesterToken = $this->getallheaders()['Token'] ?? ($_GET['apikey'] ?? false);
  1523. $apiKey = ($this->config['organizrAPI']) ?? null;
  1524. // Check token or API key
  1525. // If API key, return 0 for admin
  1526. if (strlen($requesterToken) == 20 && $requesterToken == $apiKey) {
  1527. //DO API CHECK
  1528. return 0;
  1529. } elseif (isset($this->user)) {
  1530. return $this->user['groupID'];
  1531. }
  1532. // All else fails? return guest id
  1533. return 999;
  1534. }
  1535. public function qualifyRequest($accessLevelNeeded, $api = false)
  1536. {
  1537. if ($this->getUserLevel() <= $accessLevelNeeded && $this->getUserLevel() !== null) {
  1538. return true;
  1539. } else {
  1540. if ($api) {
  1541. $this->setAPIResponse('error', 'Not Authorized', 401);
  1542. }
  1543. return false;
  1544. }
  1545. }
  1546. public function getImages()
  1547. {
  1548. $allIconsPrep = array();
  1549. $allIcons = array();
  1550. $ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id", "index.html");
  1551. $dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
  1552. $path = 'plugins/images/tabs/';
  1553. $images = scandir($dirname);
  1554. foreach ($images as $image) {
  1555. if (!in_array($image, $ignore)) {
  1556. $allIconsPrep[$image] = array(
  1557. 'path' => $path,
  1558. 'name' => $image
  1559. );
  1560. }
  1561. }
  1562. $dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
  1563. $path = 'plugins/images/userTabs/';
  1564. $images = scandir($dirname);
  1565. foreach ($images as $image) {
  1566. if (!in_array($image, $ignore)) {
  1567. $allIconsPrep[$image] = array(
  1568. 'path' => $path,
  1569. 'name' => $image
  1570. );
  1571. }
  1572. }
  1573. uksort($allIconsPrep, 'strcasecmp');
  1574. foreach ($allIconsPrep as $item) {
  1575. $allIcons[] = $item['path'] . $item['name'];
  1576. }
  1577. return $allIcons;
  1578. }
  1579. public function getImagesSelect()
  1580. {
  1581. $term = $_GET['search'] ?? null;
  1582. $page = $_GET['page'] ?? 1;
  1583. $limit = $_GET['limit'] ?? 20;
  1584. $offset = ($page * $limit) - $limit;
  1585. $goodIcons['results'] = [];
  1586. $goodIcons['limit'] = $limit;
  1587. $goodIcons['page'] = $page;
  1588. $goodIcons['term'] = $term;
  1589. $imageListing = $this->getImages();
  1590. $newImageListing = [];
  1591. foreach ($imageListing as $image) {
  1592. $newImageListing[] = [
  1593. 'id' => $image,
  1594. 'text' => basename($image)
  1595. ];
  1596. }
  1597. foreach ($newImageListing as $k => $v) {
  1598. if (stripos($v['text'], $term) !== false || !$term) {
  1599. $goodIcons['results'][] = $v;
  1600. }
  1601. }
  1602. $total = count($goodIcons['results']);
  1603. $goodIcons['total'] = $total;
  1604. $goodIcons['results'] = array_slice($goodIcons['results'], $offset, $limit);
  1605. $goodIcons['pagination']['more'] = $page < (ceil($total / $limit));
  1606. return $goodIcons;
  1607. }
  1608. public function removeImage($image = null)
  1609. {
  1610. if (!$image) {
  1611. $this->setAPIResponse('error', 'No image supplied', 422);
  1612. return false;
  1613. }
  1614. $approvedPath = 'plugins/images/userTabs/';
  1615. $removeImage = $approvedPath . pathinfo($image, PATHINFO_BASENAME);
  1616. if ($this->approvedFileExtension($removeImage, 'image')) {
  1617. if (file_exists(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . $removeImage)) {
  1618. $this->writeLog('success', 'Image Manager Function - Deleted Image [' . pathinfo($image, PATHINFO_BASENAME) . ']', $this->user['username']);
  1619. $this->setAPIResponse(null, pathinfo($image, PATHINFO_BASENAME) . ' has been deleted', null);
  1620. return (unlink(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . $removeImage));
  1621. } else {
  1622. $this->setAPIResponse('error', $removeImage . ' does not exist', 404);
  1623. return false;
  1624. }
  1625. } else {
  1626. $this->setAPIResponse('error', $removeImage . ' is not approved to be deleted', 409);
  1627. return false;
  1628. }
  1629. }
  1630. public function uploadImage()
  1631. {
  1632. $filesCheck = array_filter($_FILES);
  1633. if (!empty($filesCheck) && $this->approvedFileExtension($_FILES['file']['name'], 'image') && strpos($_FILES['file']['type'], 'image/') !== false) {
  1634. ini_set('upload_max_filesize', '10M');
  1635. ini_set('post_max_size', '10M');
  1636. $tempFile = $_FILES['file']['tmp_name'];
  1637. $targetPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
  1638. $targetFile = $targetPath . $_FILES['file']['name'];
  1639. $this->setAPIResponse(null, pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' has been uploaded', null);
  1640. return move_uploaded_file($tempFile, $targetFile);
  1641. }
  1642. }
  1643. public function ping($pings)
  1644. {
  1645. if ($this->qualifyRequest($this->config['pingAuth'], true)) {
  1646. if (!$pings['list']) {
  1647. $this->setAPIResponse('error', 'No ping hostname/IP\'s entered', 409);
  1648. return null;
  1649. }
  1650. $pings = $pings['list'];
  1651. $type = gettype($pings);
  1652. $ping = new Ping("");
  1653. $ping->setTtl(128);
  1654. $ping->setTimeout(2);
  1655. switch ($type) {
  1656. case "array":
  1657. $results = [];
  1658. foreach ($pings as $k => $v) {
  1659. if (strpos($v, ':') !== false) {
  1660. $domain = explode(':', $v)[0];
  1661. $port = explode(':', $v)[1];
  1662. $ping->setHost($domain);
  1663. $ping->setPort($port);
  1664. $latency = $ping->ping('fsockopen');
  1665. } else {
  1666. $ping->setHost($v);
  1667. $latency = $ping->ping();
  1668. }
  1669. if ($latency || $latency === 0) {
  1670. $results[$v] = $latency;
  1671. } else {
  1672. $results[$v] = false;
  1673. }
  1674. }
  1675. break;
  1676. case "string":
  1677. if (strpos($pings, ':') !== false) {
  1678. $domain = explode(':', $pings)[0];
  1679. $port = explode(':', $pings)[1];
  1680. $ping->setHost($domain);
  1681. $ping->setPort($port);
  1682. $latency = $ping->ping('fsockopen');
  1683. } else {
  1684. $ping->setHost($pings);
  1685. $latency = $ping->ping();
  1686. }
  1687. if ($latency || $latency === 0) {
  1688. $results = $latency;
  1689. } else {
  1690. $results = null;
  1691. }
  1692. break;
  1693. }
  1694. return ($results) ?? null;
  1695. }
  1696. return null;
  1697. }
  1698. public function getCustomizeAppearance()
  1699. {
  1700. return array(
  1701. 'Top Bar' => array(
  1702. array(
  1703. 'type' => 'input',
  1704. 'name' => 'logo',
  1705. 'label' => 'Logo',
  1706. 'value' => $this->config['logo']
  1707. ),
  1708. array(
  1709. 'type' => 'input',
  1710. 'name' => 'title',
  1711. 'label' => 'Title',
  1712. 'value' => $this->config['title']
  1713. ),
  1714. array(
  1715. 'type' => 'switch',
  1716. 'name' => 'useLogo',
  1717. 'label' => 'Use Logo instead of Title',
  1718. 'value' => $this->config['useLogo'],
  1719. 'help' => 'Also sets the title of your site'
  1720. ),
  1721. array(
  1722. 'type' => 'input',
  1723. 'name' => 'description',
  1724. 'label' => 'Meta Description',
  1725. 'value' => $this->config['description'],
  1726. 'help' => 'Used to set the description for SEO meta tags'
  1727. ),
  1728. ),
  1729. 'Side Menu' => array(
  1730. $this->settingsOption('switch', 'allowCollapsableSideMenu', ['label' => 'Allow Side Menu to be Collapsable']),
  1731. $this->settingsOption('switch', 'sideMenuCollapsed', ['label' => 'Side Menu Collapsed at Launch']),
  1732. $this->settingsOption('switch', 'collapseSideMenuOnClick', ['label' => 'Collapse Side Menu after clicking Tab']),
  1733. array(
  1734. 'type' => 'switch',
  1735. 'name' => 'githubMenuLink',
  1736. 'label' => 'Show GitHub Repo Link',
  1737. 'value' => $this->config['githubMenuLink']
  1738. ),
  1739. array(
  1740. 'type' => 'switch',
  1741. 'name' => 'organizrFeatureRequestLink',
  1742. 'label' => 'Show Organizr Feature Request Link',
  1743. 'value' => $this->config['organizrFeatureRequestLink']
  1744. ),
  1745. array(
  1746. 'type' => 'switch',
  1747. 'name' => 'organizrSupportMenuLink',
  1748. 'label' => 'Show Organizr Support Link',
  1749. 'value' => $this->config['organizrSupportMenuLink']
  1750. ),
  1751. array(
  1752. 'type' => 'switch',
  1753. 'name' => 'organizrDocsMenuLink',
  1754. 'label' => 'Show Organizr Docs Link',
  1755. 'value' => $this->config['organizrDocsMenuLink']
  1756. ),
  1757. array(
  1758. 'type' => 'switch',
  1759. 'name' => 'organizrSignoutMenuLink',
  1760. 'label' => 'Show Organizr Sign out & in Button on Sidebar',
  1761. 'value' => $this->config['organizrSignoutMenuLink']
  1762. ),
  1763. array(
  1764. 'type' => 'switch',
  1765. 'name' => 'expandCategoriesByDefault',
  1766. 'label' => 'Expand All Categories',
  1767. 'value' => $this->config['expandCategoriesByDefault']
  1768. ),
  1769. array(
  1770. 'type' => 'switch',
  1771. 'name' => 'autoCollapseCategories',
  1772. 'label' => 'Auto-Collapse Categories',
  1773. 'value' => $this->config['autoCollapseCategories']
  1774. ),
  1775. array(
  1776. 'type' => 'switch',
  1777. 'name' => 'autoExpandNavBar',
  1778. 'label' => 'Auto-Expand Nav Bar',
  1779. 'value' => $this->config['autoExpandNavBar']
  1780. ),
  1781. array(
  1782. 'type' => 'select',
  1783. 'name' => 'unsortedTabs',
  1784. 'label' => 'Unsorted Tab Placement',
  1785. 'value' => $this->config['unsortedTabs'],
  1786. 'options' => array(
  1787. array(
  1788. 'name' => 'Top',
  1789. 'value' => 'top'
  1790. ),
  1791. array(
  1792. 'name' => 'Bottom',
  1793. 'value' => 'bottom'
  1794. )
  1795. )
  1796. ),
  1797. ),
  1798. 'Login Page' => array(
  1799. array(
  1800. 'type' => 'input',
  1801. 'name' => 'loginLogo',
  1802. 'label' => 'Login Logo',
  1803. 'value' => $this->config['loginLogo'],
  1804. ),
  1805. array(
  1806. 'type' => 'input',
  1807. 'name' => 'loginWallpaper',
  1808. 'label' => 'Login Wallpaper',
  1809. 'value' => $this->config['loginWallpaper'],
  1810. 'help' => 'You may enter multiple URL\'s using the CSV format. i.e. link#1,link#2,link#3'
  1811. ),
  1812. array(
  1813. 'type' => 'switch',
  1814. 'name' => 'useLogoLogin',
  1815. 'label' => 'Use Logo instead of Title on Login Page',
  1816. 'value' => $this->config['useLogoLogin']
  1817. ),
  1818. array(
  1819. 'type' => 'switch',
  1820. 'name' => 'minimalLoginScreen',
  1821. 'label' => 'Minimal Login Screen',
  1822. 'value' => $this->config['minimalLoginScreen']
  1823. )
  1824. ),
  1825. 'Options' => array(
  1826. array(
  1827. 'type' => 'switch',
  1828. 'name' => 'alternateHomepageHeaders',
  1829. 'label' => 'Alternate Homepage Titles',
  1830. 'value' => $this->config['alternateHomepageHeaders']
  1831. ),
  1832. array(
  1833. 'type' => 'switch',
  1834. 'name' => 'debugErrors',
  1835. 'label' => 'Show Debug Errors',
  1836. 'value' => $this->config['debugErrors']
  1837. ),
  1838. array(
  1839. 'type' => 'switch',
  1840. 'name' => 'easterEggs',
  1841. 'label' => 'Show Easter Eggs',
  1842. 'value' => $this->config['easterEggs']
  1843. ),
  1844. array(
  1845. 'type' => 'input',
  1846. 'name' => 'gaTrackingID',
  1847. 'label' => 'Google Analytics Tracking ID',
  1848. 'placeholder' => 'e.g. UA-XXXXXXXXX-X',
  1849. 'value' => $this->config['gaTrackingID']
  1850. )
  1851. ),
  1852. 'Colors & Themes' => array(
  1853. array(
  1854. 'type' => 'html',
  1855. 'override' => 12,
  1856. 'label' => '',
  1857. 'html' => '
  1858. <div class="row">
  1859. <div class="col-lg-12">
  1860. <div class="panel panel-info">
  1861. <div class="panel-heading">
  1862. <span lang="en">Notice</span>
  1863. </div>
  1864. <div class="panel-wrapper collapse in" aria-expanded="true">
  1865. <div class="panel-body">
  1866. <span lang="en">The value of #987654 is just a placeholder, you can change to any value you like.</span>
  1867. <span lang="en">To revert back to default, save with no value defined in the relevant field.</span>
  1868. </div>
  1869. </div>
  1870. </div>
  1871. </div>
  1872. </div>
  1873. ',
  1874. ),
  1875. array(
  1876. 'type' => 'blank',
  1877. 'label' => ''
  1878. ),
  1879. $this->settingsOption('button', '', ['label' => 'Reset Colors', 'icon' => 'fa fa-ticket', 'text' => 'Reset', 'attr' => 'onclick="resetCustomColors()"']),
  1880. $this->settingsOption('blank'),
  1881. array(
  1882. 'type' => 'input',
  1883. 'name' => 'headerColor',
  1884. 'label' => 'Nav Bar Color',
  1885. 'value' => $this->config['headerColor'],
  1886. 'class' => 'pick-a-color-custom-options',
  1887. 'attr' => 'data-original="' . $this->config['headerColor'] . '"'
  1888. ),
  1889. array(
  1890. 'type' => 'input',
  1891. 'name' => 'headerTextColor',
  1892. 'label' => 'Nav Bar Text Color',
  1893. 'value' => $this->config['headerTextColor'],
  1894. 'class' => 'pick-a-color-custom-options',
  1895. 'attr' => 'data-original="' . $this->config['headerTextColor'] . '"'
  1896. ),
  1897. array(
  1898. 'type' => 'input',
  1899. 'name' => 'sidebarColor',
  1900. 'label' => 'Side Bar Color',
  1901. 'value' => $this->config['sidebarColor'],
  1902. 'class' => 'pick-a-color-custom-options',
  1903. 'attr' => 'data-original="' . $this->config['sidebarColor'] . '"'
  1904. ),
  1905. array(
  1906. 'type' => 'input',
  1907. 'name' => 'sidebarTextColor',
  1908. 'label' => 'Side Bar Text Color',
  1909. 'value' => $this->config['sidebarTextColor'],
  1910. 'class' => 'pick-a-color-custom-options',
  1911. 'attr' => 'data-original="' . $this->config['sidebarTextColor'] . '"'
  1912. ),
  1913. array(
  1914. 'type' => 'input',
  1915. 'name' => 'accentColor',
  1916. 'label' => 'Accent Color',
  1917. 'value' => $this->config['accentColor'],
  1918. 'class' => 'pick-a-color-custom-options',
  1919. 'attr' => 'data-original="' . $this->config['accentColor'] . '"'
  1920. ),
  1921. array(
  1922. 'type' => 'input',
  1923. 'name' => 'accentTextColor',
  1924. 'label' => 'Accent Text Color',
  1925. 'value' => $this->config['accentTextColor'],
  1926. 'class' => 'pick-a-color-custom-options',
  1927. 'attr' => 'data-original="' . $this->config['accentTextColor'] . '"'
  1928. ),
  1929. array(
  1930. 'type' => 'input',
  1931. 'name' => 'buttonColor',
  1932. 'label' => 'Button Color',
  1933. 'value' => $this->config['buttonColor'],
  1934. 'class' => 'pick-a-color-custom-options',
  1935. 'attr' => 'data-original="' . $this->config['buttonColor'] . '"'
  1936. ),
  1937. array(
  1938. 'type' => 'input',
  1939. 'name' => 'buttonTextColor',
  1940. 'label' => 'Button Text Color',
  1941. 'value' => $this->config['buttonTextColor'],
  1942. 'class' => 'pick-a-color-custom-options',
  1943. 'attr' => 'data-original="' . $this->config['buttonTextColor'] . '"'
  1944. ),
  1945. array(
  1946. 'type' => 'select',
  1947. 'name' => 'theme',
  1948. 'label' => 'Theme',
  1949. 'class' => 'themeChanger',
  1950. 'value' => $this->config['theme'],
  1951. 'options' => $this->getThemes()
  1952. ),
  1953. array(
  1954. 'type' => 'select',
  1955. 'name' => 'style',
  1956. 'label' => 'Style',
  1957. 'class' => 'styleChanger',
  1958. 'value' => $this->config['style'],
  1959. 'options' => array(
  1960. array(
  1961. 'name' => 'Light',
  1962. 'value' => 'light'
  1963. ),
  1964. array(
  1965. 'name' => 'Dark',
  1966. 'value' => 'dark'
  1967. ),
  1968. array(
  1969. 'name' => 'Horizontal',
  1970. 'value' => 'horizontal'
  1971. )
  1972. )
  1973. )
  1974. ),
  1975. 'Notifications' => array(
  1976. array(
  1977. 'type' => 'select',
  1978. 'name' => 'notificationBackbone',
  1979. 'class' => 'notifyChanger',
  1980. 'label' => 'Type',
  1981. 'value' => $this->config['notificationBackbone'],
  1982. 'options' => $this->notificationTypesOptions()
  1983. ),
  1984. array(
  1985. 'type' => 'select',
  1986. 'name' => 'notificationPosition',
  1987. 'class' => 'notifyPositionChanger',
  1988. 'label' => 'Position',
  1989. 'value' => $this->config['notificationPosition'],
  1990. 'options' => $this->notificationPositionsOptions()
  1991. ),
  1992. array(
  1993. 'type' => 'html',
  1994. 'label' => 'Test Message',
  1995. 'html' => '
  1996. <div class="btn-group m-r-10 dropup">
  1997. <button aria-expanded="false" data-toggle="dropdown" class="btn btn-info btn-outline dropdown-toggle waves-effect waves-light" type="button">
  1998. <i class="fa fa-comment m-r-5"></i>
  1999. <span>Test </span>
  2000. </button>
  2001. <ul role="menu" class="dropdown-menu">
  2002. <li><a onclick="message(\'Test Message\',\'This is a success Message\',activeInfo.settings.notifications.position,\'#FFF\',\'success\',\'5000\');">Success</a></li>
  2003. <li><a onclick="message(\'Test Message\',\'This is a info Message\',activeInfo.settings.notifications.position,\'#FFF\',\'info\',\'5000\');">Info</a></li>
  2004. <li><a onclick="message(\'Test Message\',\'This is a warning Message\',activeInfo.settings.notifications.position,\'#FFF\',\'warning\',\'5000\');">Warning</a></li>
  2005. <li><a onclick="message(\'Test Message\',\'This is a error Message\',activeInfo.settings.notifications.position,\'#FFF\',\'error\',\'5000\');">Error</a></li>
  2006. </ul>
  2007. </div>
  2008. '
  2009. )
  2010. ),
  2011. 'FavIcon' => array(
  2012. array(
  2013. 'type' => 'textbox',
  2014. 'name' => 'favIcon',
  2015. 'class' => '',
  2016. 'label' => 'Fav Icon Code',
  2017. 'value' => $this->config['favIcon'],
  2018. 'placeholder' => 'Paste Contents from https://realfavicongenerator.net/',
  2019. 'attr' => 'rows="10"',
  2020. ),
  2021. array(
  2022. 'type' => 'html',
  2023. 'label' => 'Instructions',
  2024. 'html' => '
  2025. <div class="panel panel-default">
  2026. <div class="panel-heading">
  2027. <a href="https://realfavicongenerator.net/" target="_blank"><span class="label label-info m-l-5">Visit FavIcon Site</span></a>
  2028. </div>
  2029. <div class="panel-wrapper collapse in">
  2030. <div class="panel-body">
  2031. <ul class="list-icons">
  2032. <li lang="en"><i class="fa fa-caret-right text-info"></i> Click [Select your Favicon picture]</li>
  2033. <li lang="en"><i class="fa fa-caret-right text-info"></i> Choose your image to use</li>
  2034. <li lang="en"><i class="fa fa-caret-right text-info"></i> Edit settings to your liking</li>
  2035. <li lang="en"><i class="fa fa-caret-right text-info"></i> At bottom of page on [Favicon Generator Options] under [Path] choose [I cannot or I do not want to place favicon files at the root of my web site.]</li>
  2036. <li lang="en"><i class="fa fa-caret-right text-info"></i> Enter this path <code>plugins/images/faviconCustom</code></li>
  2037. <li lang="en"><i class="fa fa-caret-right text-info"></i> Click [Generate your Favicons and HTML code]</li>
  2038. <li lang="en"><i class="fa fa-caret-right text-info"></i> Download and unzip file and place in <code>plugins/images/faviconCustom</code></li>
  2039. <li lang="en"><i class="fa fa-caret-right text-info"></i> Copy code and paste inside left box</li>
  2040. </ul>
  2041. </div>
  2042. </div>
  2043. </div>
  2044. '
  2045. ),
  2046. ),
  2047. 'Custom CSS' => array(
  2048. array(
  2049. 'type' => 'html',
  2050. 'override' => 12,
  2051. 'label' => 'Custom CSS [Can replace colors from above]',
  2052. 'html' => '<button type="button" class="hidden saveCss btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customCSSEditor" style="height:300px">' . htmlentities($this->config['customCss']) . '</div>'
  2053. ),
  2054. array(
  2055. 'type' => 'textbox',
  2056. 'name' => 'customCss',
  2057. 'class' => 'hidden cssTextarea',
  2058. 'label' => '',
  2059. 'value' => $this->config['customCss'],
  2060. 'placeholder' => 'No &lt;style&gt; tags needed',
  2061. 'attr' => 'rows="10"',
  2062. ),
  2063. ),
  2064. 'Theme CSS' => array(
  2065. array(
  2066. 'type' => 'html',
  2067. 'override' => 12,
  2068. 'label' => 'Theme CSS [Can replace colors from above]',
  2069. 'html' => '<button type="button" class="hidden saveCssTheme btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customThemeCSSEditor" style="height:300px">' . htmlentities($this->config['customThemeCss']) . '</div>'
  2070. ),
  2071. array(
  2072. 'type' => 'textbox',
  2073. 'name' => 'customThemeCss',
  2074. 'class' => 'hidden cssThemeTextarea',
  2075. 'label' => '',
  2076. 'value' => $this->config['customThemeCss'],
  2077. 'placeholder' => 'No &lt;style&gt; tags needed',
  2078. 'attr' => 'rows="10"',
  2079. ),
  2080. ),
  2081. 'Custom Javascript' => array(
  2082. array(
  2083. 'type' => 'html',
  2084. 'override' => 12,
  2085. 'label' => 'Custom Javascript',
  2086. 'html' => '<button type="button" class="hidden saveJava btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customJavaEditor" style="height:300px">' . htmlentities($this->config['customJava']) . '</div>'
  2087. ),
  2088. array(
  2089. 'type' => 'textbox',
  2090. 'name' => 'customJava',
  2091. 'class' => 'hidden javaTextarea',
  2092. 'label' => '',
  2093. 'value' => $this->config['customJava'],
  2094. 'placeholder' => 'No &lt;script&gt; tags needed',
  2095. 'attr' => 'rows="10"',
  2096. ),
  2097. ),
  2098. 'Theme Javascript' => array(
  2099. array(
  2100. 'type' => 'html',
  2101. 'override' => 12,
  2102. 'label' => 'Theme Javascript',
  2103. 'html' => '<button type="button" class="hidden saveJavaTheme btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customThemeJavaEditor" style="height:300px">' . htmlentities($this->config['customThemeJava']) . '</div>'
  2104. ),
  2105. array(
  2106. 'type' => 'textbox',
  2107. 'name' => 'customThemeJava',
  2108. 'class' => 'hidden javaThemeTextarea',
  2109. 'label' => '',
  2110. 'value' => $this->config['customThemeJava'],
  2111. 'placeholder' => 'No &lt;script&gt; tags needed',
  2112. 'attr' => 'rows="10"',
  2113. ),
  2114. ),
  2115. );
  2116. }
  2117. public function loadAppearance()
  2118. {
  2119. $appearance['logo'] = $this->config['logo'];
  2120. $appearance['title'] = $this->config['title'];
  2121. $appearance['useLogo'] = $this->config['useLogo'];
  2122. $appearance['useLogoLogin'] = $this->config['useLogoLogin'];
  2123. $appearance['headerColor'] = $this->config['headerColor'];
  2124. $appearance['headerTextColor'] = $this->config['headerTextColor'];
  2125. $appearance['sidebarColor'] = $this->config['sidebarColor'];
  2126. $appearance['headerTextColor'] = $this->config['headerTextColor'];
  2127. $appearance['sidebarTextColor'] = $this->config['sidebarTextColor'];
  2128. $appearance['accentColor'] = $this->config['accentColor'];
  2129. $appearance['accentTextColor'] = $this->config['accentTextColor'];
  2130. $appearance['buttonColor'] = $this->config['buttonColor'];
  2131. $appearance['buttonTextColor'] = $this->config['buttonTextColor'];
  2132. $appearance['buttonTextHoverColor'] = $this->config['buttonTextHoverColor'];
  2133. $appearance['buttonHoverColor'] = $this->config['buttonHoverColor'];
  2134. $appearance['loginWallpaper'] = $this->config['loginWallpaper'];
  2135. $appearance['loginLogo'] = $this->config['loginLogo'];
  2136. $appearance['customCss'] = $this->config['customCss'];
  2137. $appearance['customThemeCss'] = $this->config['customThemeCss'];
  2138. $appearance['customJava'] = $this->config['customJava'];
  2139. $appearance['customThemeJava'] = $this->config['customThemeJava'];
  2140. return $appearance;
  2141. }
  2142. public function getSettingsMain()
  2143. {
  2144. $certificateStatus = $this->hasCustomCert() ? '<span lang="en">Custom Certificate Loaded</span><br />Located at <span>' . $this->getCustomCert() . '</span>' : '<span lang="en">Custom Certificate not found - please upload below</span>';
  2145. return array(
  2146. 'Settings Page' => array(
  2147. array(
  2148. 'type' => 'select',
  2149. 'name' => 'defaultSettingsTab',
  2150. 'label' => 'Default Settings Tab',
  2151. 'value' => $this->config['defaultSettingsTab'],
  2152. 'options' => $this->getSettingsTabs(),
  2153. 'help' => 'Choose which Settings Tab to be default when opening settings page'
  2154. ),
  2155. ),
  2156. 'Database' => [
  2157. $this->settingsOption('notice', '', ['notice' => 'danger', 'title' => 'Warning', 'body' => 'This feature is experimental - You may face unexpected database is locked errors in logs']),
  2158. $this->settingsOption('html', '', ['label' => 'Journal Mode Status', 'html' => '<script>getJournalMode();</script><h4 class="journal-mode font-bold text-uppercase"><i class="fa fa-spin fa-circle-o-notch"></i></h4>']),
  2159. //$this->settingsOption('blank'),
  2160. $this->settingsOption('button', '', ['label' => 'Set DELETE Mode (Default)', 'icon' => 'icon-notebook', 'text' => 'Set', 'attr' => 'onclick="setJournalMode(\'DELETE\')"']),
  2161. $this->settingsOption('button', '', ['label' => 'Set WAL Mode', 'icon' => 'icon-notebook', 'text' => 'Set', 'attr' => 'onclick="setJournalMode(\'WAL\')"']),
  2162. ],
  2163. 'Github' => array(
  2164. array(
  2165. 'type' => 'select',
  2166. 'name' => 'branch',
  2167. 'label' => 'Branch',
  2168. 'value' => $this->config['branch'],
  2169. 'options' => $this->getBranches(),
  2170. 'disabled' => $this->docker,
  2171. 'help' => ($this->docker) ? 'Since you are using the Official Docker image, Change the image to change the branch' : 'Choose which branch to download from'
  2172. ),
  2173. array(
  2174. 'type' => 'button',
  2175. 'name' => 'force-install-branch',
  2176. 'label' => 'Force Install Branch',
  2177. 'class' => 'updateNow',
  2178. 'icon' => 'fa fa-download',
  2179. 'text' => 'Retrieve',
  2180. 'attr' => ($this->docker) ? 'title="You can just restart your docker to update"' : '',
  2181. 'help' => ($this->docker) ? 'Since you are using the official Docker image, you can just restart your Docker container to update Organizr' : 'This will re-download all of the source files for Organizr'
  2182. )
  2183. ),
  2184. 'API' => array(
  2185. array(
  2186. 'type' => 'password-alt-copy',
  2187. 'name' => 'organizrAPI',
  2188. 'label' => 'Organizr API',
  2189. 'value' => $this->config['organizrAPI']
  2190. ),
  2191. array(
  2192. 'type' => 'button',
  2193. 'label' => 'Generate New API Key',
  2194. 'class' => 'newAPIKey',
  2195. 'icon' => 'fa fa-refresh',
  2196. 'text' => 'Generate'
  2197. ),
  2198. $this->settingsOption('notice', null, ['title' => 'API Documentation', 'body' => 'The documentation for Organizr\'s API is included with this installation. To access the docs, use the button below.', 'bodyHTML' => '<br/><br/><div class="row"><div class="col-lg-2 col-sm-4 col-xs-12"><a href="' . $this->getServerPath() . 'docs/" target="_blank" class="btn btn-block btn-primary text-white" lang="en">Organizr Docs</a></div></div>'])
  2199. ),
  2200. 'Authentication' => array(
  2201. array(
  2202. 'type' => 'select',
  2203. 'name' => 'authType',
  2204. 'id' => 'authSelect',
  2205. 'label' => 'Authentication Type',
  2206. 'value' => $this->config['authType'],
  2207. 'options' => $this->getAuthTypes()
  2208. ),
  2209. array(
  2210. 'type' => 'select',
  2211. 'name' => 'authBackend',
  2212. 'id' => 'authBackendSelect',
  2213. 'label' => 'Authentication Backend',
  2214. 'class' => 'backendAuth switchAuth',
  2215. 'value' => $this->config['authBackend'],
  2216. 'options' => $this->getAuthBackends()
  2217. ),
  2218. array(
  2219. 'type' => 'password-alt',
  2220. 'name' => 'plexToken',
  2221. 'class' => 'plexAuth switchAuth',
  2222. 'label' => 'Plex Token',
  2223. 'value' => $this->config['plexToken'],
  2224. 'placeholder' => 'Use Get Token Button'
  2225. ),
  2226. array(
  2227. 'type' => 'button',
  2228. 'label' => 'Get Plex Token',
  2229. 'class' => 'getPlexTokenAuth plexAuth switchAuth',
  2230. 'icon' => 'fa fa-ticket',
  2231. 'text' => 'Retrieve',
  2232. 'attr' => 'onclick="PlexOAuth(oAuthSuccess,oAuthError, null, \'#settings-main-form [name=plexToken]\')"'
  2233. ),
  2234. array(
  2235. 'type' => 'password-alt',
  2236. 'name' => 'plexID',
  2237. 'class' => 'plexAuth switchAuth',
  2238. 'label' => 'Plex Machine',
  2239. 'value' => $this->config['plexID'],
  2240. 'placeholder' => 'Use Get Plex Machine Button'
  2241. ),
  2242. array(
  2243. 'type' => 'button',
  2244. 'label' => 'Get Plex Machine',
  2245. 'class' => 'getPlexMachineAuth plexAuth switchAuth',
  2246. 'icon' => 'fa fa-id-badge',
  2247. 'text' => 'Retrieve',
  2248. 'attr' => 'onclick="showPlexMachineForm(\'#settings-main-form [name=plexID]\')"'
  2249. ),
  2250. array(
  2251. 'type' => 'input',
  2252. 'name' => 'plexAdmin',
  2253. 'label' => 'Plex Admin Username',
  2254. 'class' => 'plexAuth switchAuth',
  2255. 'value' => $this->config['plexAdmin'],
  2256. 'placeholder' => 'Admin username for Plex'
  2257. ),
  2258. array(
  2259. 'type' => 'switch',
  2260. 'name' => 'plexoAuth',
  2261. 'label' => 'Enable Plex oAuth',
  2262. 'class' => 'plexAuth switchAuth',
  2263. 'value' => $this->config['plexoAuth']
  2264. ),
  2265. array(
  2266. 'type' => 'switch',
  2267. 'name' => 'ignoreTFAIfPlexOAuth',
  2268. 'label' => 'Ignore 2FA if Plex OAuth ',
  2269. 'class' => 'plexAuth switchAuth',
  2270. 'value' => $this->config['ignoreTFAIfPlexOAuth'],
  2271. 'help' => 'Enabling this will disable Organizr 2FA (If applicable) if User uses Plex OAuth to login'
  2272. ),
  2273. array(
  2274. 'type' => 'switch',
  2275. 'name' => 'plexStrictFriends',
  2276. 'label' => 'Strict Plex Friends ',
  2277. 'class' => 'plexAuth switchAuth',
  2278. 'value' => $this->config['plexStrictFriends'],
  2279. 'help' => 'Enabling this will only allow Friends that have shares to the Machine ID entered above to login, Having this disabled will allow all Friends on your Friends list to login'
  2280. ),
  2281. array(
  2282. 'type' => 'switch',
  2283. 'name' => 'ignoreTFALocal',
  2284. 'label' => 'Ignore External 2FA on Local Subnet',
  2285. 'value' => $this->config['ignoreTFALocal'],
  2286. 'help' => 'Enabling this will bypass external 2FA security if user is on local Subnet'
  2287. ),
  2288. array(
  2289. 'type' => 'input',
  2290. 'name' => 'authBackendHost',
  2291. 'class' => 'ldapAuth ftpAuth switchAuth',
  2292. 'label' => 'Host Address',
  2293. 'value' => $this->config['authBackendHost'],
  2294. 'placeholder' => 'http(s) | ftp(s) | ldap(s)://hostname:port'
  2295. ),
  2296. array(
  2297. 'type' => 'input',
  2298. 'name' => 'authBaseDN',
  2299. 'class' => 'ldapAuth switchAuth',
  2300. 'label' => 'Host Base DN',
  2301. 'value' => $this->config['authBaseDN'],
  2302. 'placeholder' => 'cn=%s,dc=sub,dc=domain,dc=com'
  2303. ),
  2304. array(
  2305. 'type' => 'input',
  2306. 'name' => 'authBackendHostPrefix',
  2307. 'class' => 'ldapAuth switchAuth',
  2308. 'label' => 'Account Prefix',
  2309. 'id' => 'authBackendHostPrefix-input',
  2310. 'value' => $this->config['authBackendHostPrefix'],
  2311. 'placeholder' => 'Account prefix - i.e. Controller\ from Controller\Username for AD - uid= for OpenLDAP'
  2312. ),
  2313. array(
  2314. 'type' => 'input',
  2315. 'name' => 'authBackendHostSuffix',
  2316. 'class' => 'ldapAuth switchAuth',
  2317. 'label' => 'Account Suffix',
  2318. 'id' => 'authBackendHostSuffix-input',
  2319. 'value' => $this->config['authBackendHostSuffix'],
  2320. 'placeholder' => 'Account suffix - start with comma - ,ou=people,dc=domain,dc=tld'
  2321. ),
  2322. array(
  2323. 'type' => 'input',
  2324. 'name' => 'ldapBindUsername',
  2325. 'class' => 'ldapAuth switchAuth',
  2326. 'label' => 'Bind Username',
  2327. 'value' => $this->config['ldapBindUsername'],
  2328. 'placeholder' => ''
  2329. ),
  2330. array(
  2331. 'type' => 'password',
  2332. 'name' => 'ldapBindPassword',
  2333. 'class' => 'ldapAuth switchAuth',
  2334. 'label' => 'Bind Password',
  2335. 'value' => $this->config['ldapBindPassword']
  2336. ),
  2337. array(
  2338. 'type' => 'select',
  2339. 'name' => 'ldapType',
  2340. 'id' => 'ldapType',
  2341. 'label' => 'LDAP Backend Type',
  2342. 'class' => 'ldapAuth switchAuth',
  2343. 'value' => $this->config['ldapType'],
  2344. 'options' => $this->getLDAPOptions()
  2345. ),
  2346. array(
  2347. 'type' => 'html',
  2348. 'class' => 'ldapAuth switchAuth',
  2349. 'label' => 'Account DN',
  2350. 'html' => '<span id="accountDN" class="ldapAuth switchAuth">' . $this->config['authBackendHostPrefix'] . 'TestAcct' . $this->config['authBackendHostSuffix'] . '</span>'
  2351. ),
  2352. array(
  2353. 'type' => 'switch',
  2354. 'name' => 'ldapSSL',
  2355. 'class' => 'ldapAuth switchAuth',
  2356. 'label' => 'Enable LDAP SSL',
  2357. 'value' => $this->config['ldapSSL'],
  2358. 'help' => 'This will enable the use of SSL for LDAP connections'
  2359. ),
  2360. array(
  2361. 'type' => 'switch',
  2362. 'name' => 'ldapSSL',
  2363. 'class' => 'ldapAuth switchAuth',
  2364. 'label' => 'Enable LDAP TLS',
  2365. 'value' => $this->config['ldapTLS'],
  2366. 'help' => 'This will enable the use of TLS for LDAP connections'
  2367. ),
  2368. array(
  2369. 'type' => 'button',
  2370. 'name' => 'test-button-ldap',
  2371. 'label' => 'Test Connection',
  2372. 'icon' => 'fa fa-flask',
  2373. 'class' => 'ldapAuth switchAuth',
  2374. 'text' => 'Test Connection',
  2375. 'attr' => 'onclick="testAPIConnection(\'ldap\')"',
  2376. 'help' => 'Remember! Please save before using the test button!'
  2377. ),
  2378. array(
  2379. 'type' => 'button',
  2380. 'name' => 'test-button-ldap-login',
  2381. 'label' => 'Test Login',
  2382. 'icon' => 'fa fa-flask',
  2383. 'class' => 'ldapAuth switchAuth',
  2384. 'text' => 'Test Login',
  2385. 'attr' => 'onclick="showLDAPLoginTest()"'
  2386. ),
  2387. array(
  2388. 'type' => 'input',
  2389. 'name' => 'embyURL',
  2390. 'class' => 'embyAuth switchAuth',
  2391. 'label' => 'Emby URL',
  2392. 'value' => $this->config['embyURL'],
  2393. 'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
  2394. 'placeholder' => 'http(s)://hostname:port'
  2395. ),
  2396. array(
  2397. 'type' => 'password-alt',
  2398. 'name' => 'embyToken',
  2399. 'class' => 'embyAuth switchAuth',
  2400. 'label' => 'Emby Token',
  2401. 'value' => $this->config['embyToken'],
  2402. 'placeholder' => ''
  2403. ),
  2404. array(
  2405. 'type' => 'input',
  2406. 'name' => 'jellyfinURL',
  2407. 'class' => 'jellyfinAuth switchAuth',
  2408. 'label' => 'Jellyfin URL',
  2409. 'value' => $this->config['jellyfinURL'],
  2410. 'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
  2411. 'placeholder' => 'http(s)://hostname:port'
  2412. ),
  2413. array(
  2414. 'type' => 'password-alt',
  2415. 'name' => 'jellyfinToken',
  2416. 'class' => 'jellyfinAuth switchAuth',
  2417. 'label' => 'Jellyfin Token',
  2418. 'value' => $this->config['jellyfinToken'],
  2419. 'placeholder' => ''
  2420. ),
  2421. ),
  2422. 'Security' => array(
  2423. array(
  2424. 'type' => 'number',
  2425. 'name' => 'loginAttempts',
  2426. 'label' => 'Max Login Attempts',
  2427. 'value' => $this->config['loginAttempts'],
  2428. 'placeholder' => ''
  2429. ),
  2430. array(
  2431. 'type' => 'select',
  2432. 'name' => 'loginLockout',
  2433. 'label' => 'Login Lockout Seconds',
  2434. 'value' => $this->config['loginLockout'],
  2435. 'options' => $this->timeOptions()
  2436. ),
  2437. array(
  2438. 'type' => 'number',
  2439. 'name' => 'lockoutTimeout',
  2440. 'label' => 'Inactivity Timer [Minutes]',
  2441. 'value' => $this->config['lockoutTimeout'],
  2442. 'placeholder' => ''
  2443. ),
  2444. array(
  2445. 'type' => 'switch',
  2446. 'name' => 'lockoutSystem',
  2447. 'label' => 'Inactivity Lock',
  2448. 'value' => $this->config['lockoutSystem']
  2449. ),
  2450. array(
  2451. 'type' => 'select',
  2452. 'name' => 'lockoutMinAuth',
  2453. 'label' => 'Lockout Groups From',
  2454. 'value' => $this->config['lockoutMinAuth'],
  2455. 'options' => $this->groupSelect()
  2456. ),
  2457. array(
  2458. 'type' => 'select',
  2459. 'name' => 'lockoutMaxAuth',
  2460. 'label' => 'Lockout Groups To',
  2461. 'value' => $this->config['lockoutMaxAuth'],
  2462. 'options' => $this->groupSelect()
  2463. ),
  2464. array(
  2465. 'type' => 'switch',
  2466. 'name' => 'traefikAuthEnable',
  2467. 'label' => 'Enable Traefik Auth Redirect',
  2468. 'help' => 'This will enable the webserver to forward errors so traefik will accept them',
  2469. 'value' => $this->config['traefikAuthEnable']
  2470. ),
  2471. array(
  2472. 'type' => 'input',
  2473. 'name' => 'traefikDomainOverride',
  2474. 'label' => 'Traefik Domain for Return Override',
  2475. 'value' => $this->config['traefikDomainOverride'],
  2476. 'help' => 'Please use a FQDN on this URL Override',
  2477. 'placeholder' => 'http(s)://domain'
  2478. ),
  2479. array(
  2480. 'type' => 'select',
  2481. 'name' => 'debugAreaAuth',
  2482. 'label' => 'Minimum Authentication for Debug Area',
  2483. 'value' => $this->config['debugAreaAuth'],
  2484. 'options' => $this->groupSelect(),
  2485. 'settings' => '{}'
  2486. ),
  2487. array(
  2488. 'type' => 'select2',
  2489. 'class' => 'select2-multiple',
  2490. 'id' => 'sandbox-select',
  2491. 'name' => 'sandbox',
  2492. 'label' => 'iFrame Sandbox',
  2493. 'value' => $this->config['sandbox'],
  2494. 'help' => 'WARNING! This can potentially mess up your iFrames',
  2495. 'options' => array(
  2496. array(
  2497. 'name' => 'Allow Presentation',
  2498. 'value' => 'allow-presentation'
  2499. ),
  2500. array(
  2501. 'name' => 'Allow Forms',
  2502. 'value' => 'allow-forms'
  2503. ),
  2504. array(
  2505. 'name' => 'Allow Same Origin',
  2506. 'value' => 'allow-same-origin'
  2507. ),
  2508. array(
  2509. 'name' => 'Allow Orientation Lock',
  2510. 'value' => 'allow-orientation-lock'
  2511. ),
  2512. array(
  2513. 'name' => 'Allow Pointer Lock',
  2514. 'value' => 'allow-pointer-lock'
  2515. ),
  2516. array(
  2517. 'name' => 'Allow Scripts',
  2518. 'value' => 'allow-scripts'
  2519. ),
  2520. array(
  2521. 'name' => 'Allow Popups',
  2522. 'value' => 'allow-popups'
  2523. ),
  2524. array(
  2525. 'name' => 'Allow Popups To Escape Sandbox',
  2526. 'value' => 'allow-popups-to-escape-sandbox'
  2527. ),
  2528. array(
  2529. 'name' => 'Allow Modals',
  2530. 'value' => 'allow-modals'
  2531. ),
  2532. array(
  2533. 'name' => 'Allow Top Navigation',
  2534. 'value' => 'allow-top-navigation'
  2535. ),
  2536. array(
  2537. 'name' => 'Allow Top Navigation By User Activation',
  2538. 'value' => 'allow-top-navigation-by-user-activation'
  2539. ),
  2540. array(
  2541. 'name' => 'Allow Downloads',
  2542. 'value' => 'allow-downloads'
  2543. ),
  2544. )
  2545. ),
  2546. array(
  2547. 'type' => 'select2',
  2548. 'class' => 'select2-multiple',
  2549. 'id' => 'blacklisted-select',
  2550. 'name' => 'blacklisted',
  2551. 'label' => 'Blacklisted IP\'s',
  2552. 'value' => $this->config['blacklisted'],
  2553. 'help' => 'WARNING! This will block anyone with these IP\'s',
  2554. 'options' => $this->makeOptionsFromValues($this->config['blacklisted']),
  2555. 'settings' => '{tags: true}',
  2556. ),
  2557. array(
  2558. 'type' => 'textbox',
  2559. 'name' => 'blacklistedMessage',
  2560. 'class' => '',
  2561. 'label' => 'Blacklisted Error Message',
  2562. 'value' => $this->config['blacklistedMessage'],
  2563. 'attr' => 'rows="10"',
  2564. ),
  2565. ),
  2566. 'Login' => array(
  2567. array(
  2568. 'type' => 'password-alt',
  2569. 'name' => 'registrationPassword',
  2570. 'label' => 'Registration Password',
  2571. 'help' => 'Sets the password for the Registration form on the login screen',
  2572. 'value' => $this->config['registrationPassword'],
  2573. ),
  2574. array(
  2575. 'type' => 'switch',
  2576. 'name' => 'hideRegistration',
  2577. 'label' => 'Hide Registration',
  2578. 'help' => 'Enable this to hide the Registration button on the login screen',
  2579. 'value' => $this->config['hideRegistration'],
  2580. ),
  2581. array(
  2582. 'type' => 'number',
  2583. 'name' => 'rememberMeDays',
  2584. 'label' => 'Remember Me Length',
  2585. 'help' => 'Number of days cookies and tokens will be valid for',
  2586. 'value' => $this->config['rememberMeDays'],
  2587. 'placeholder' => '',
  2588. 'attr' => 'min="1"'
  2589. ),
  2590. array(
  2591. 'type' => 'switch',
  2592. 'name' => 'rememberMe',
  2593. 'label' => 'Remember Me',
  2594. 'help' => 'Default status of Remember Me button on login screen',
  2595. 'value' => $this->config['rememberMe'],
  2596. ),
  2597. $this->settingsOption('multiple-url', 'localIPList', ['label' => 'Override Local IP or Subnet', 'help' => 'IPv4 only at the moment - This will set your login as local if your IP falls within the From and To']),
  2598. array(
  2599. 'type' => 'input',
  2600. 'name' => 'wanDomain',
  2601. 'label' => 'WAN Domain',
  2602. 'value' => $this->config['wanDomain'],
  2603. 'placeholder' => 'only domain and tld - i.e. domain.com',
  2604. 'help' => 'Enter domain if you wish to be forwarded to a local address - Local Address filled out on next item'
  2605. ),
  2606. array(
  2607. 'type' => 'input',
  2608. 'name' => 'localAddress',
  2609. 'label' => 'Local Address',
  2610. 'value' => $this->config['localAddress'],
  2611. 'placeholder' => 'http://home.local',
  2612. 'help' => 'Full local address of organizr install - i.e. http://home.local or http://192.168.0.100'
  2613. ),
  2614. array(
  2615. 'type' => 'switch',
  2616. 'name' => 'enableLocalAddressForward',
  2617. 'label' => 'Enable Local Address Forward',
  2618. 'help' => 'Enables the local address forward if on local address and accessed from WAN Domain',
  2619. 'value' => $this->config['enableLocalAddressForward'],
  2620. ),
  2621. array(
  2622. 'type' => 'switch',
  2623. 'name' => 'disableRecoverPass',
  2624. 'label' => 'Disable Recover Password',
  2625. 'help' => 'Disables recover password area',
  2626. 'value' => $this->config['disableRecoverPass'],
  2627. ),
  2628. array(
  2629. 'type' => 'input',
  2630. 'name' => 'customForgotPassText',
  2631. 'label' => 'Custom Recover Password Text',
  2632. 'value' => $this->config['customForgotPassText'],
  2633. 'placeholder' => '',
  2634. 'help' => 'Text or HTML for recovery password section'
  2635. ),
  2636. ),
  2637. 'Auth Proxy' => array(
  2638. array(
  2639. 'type' => 'switch',
  2640. 'name' => 'authProxyEnabled',
  2641. 'label' => 'Auth Proxy',
  2642. 'help' => 'Enable option to set Auth Proxy Header Login',
  2643. 'value' => $this->config['authProxyEnabled'],
  2644. ),
  2645. array(
  2646. 'type' => 'input',
  2647. 'name' => 'authProxyWhitelist',
  2648. 'label' => 'Auth Proxy Whitelist',
  2649. 'value' => $this->config['authProxyWhitelist'],
  2650. 'placeholder' => 'i.e. 10.0.0.0/24 or 10.0.0.20',
  2651. 'help' => 'IPv4 only at the moment - This must be set to work, will accept subnet or IP address'
  2652. ),
  2653. array(
  2654. 'type' => 'input',
  2655. 'name' => 'authProxyHeaderName',
  2656. 'label' => 'Auth Proxy Header Name',
  2657. 'value' => $this->config['authProxyHeaderName'],
  2658. 'placeholder' => 'i.e. X-Forwarded-User',
  2659. 'help' => 'Please choose a unique value for added security'
  2660. ),
  2661. array(
  2662. 'type' => 'input',
  2663. 'name' => 'authProxyHeaderNameEmail',
  2664. 'label' => 'Auth Proxy Header Name for Email',
  2665. 'value' => $this->config['authProxyHeaderNameEmail'],
  2666. 'placeholder' => 'i.e. X-Forwarded-Email',
  2667. 'help' => 'Please choose a unique value for added security'
  2668. )
  2669. ),
  2670. 'Ping' => array(
  2671. array(
  2672. 'type' => 'select',
  2673. 'name' => 'pingAuth',
  2674. 'label' => 'Minimum Authentication',
  2675. 'value' => $this->config['pingAuth'],
  2676. 'options' => $this->groupSelect()
  2677. ),
  2678. array(
  2679. 'type' => 'select',
  2680. 'name' => 'pingAuthMessage',
  2681. 'label' => 'Minimum Authentication for Message and Sound',
  2682. 'value' => $this->config['pingAuthMessage'],
  2683. 'options' => $this->groupSelect()
  2684. ),
  2685. array(
  2686. 'type' => 'select',
  2687. 'name' => 'pingOnlineSound',
  2688. 'label' => 'Online Sound',
  2689. 'value' => $this->config['pingOnlineSound'],
  2690. 'options' => $this->getSounds()
  2691. ),
  2692. array(
  2693. 'type' => 'select',
  2694. 'name' => 'pingOfflineSound',
  2695. 'label' => 'Offline Sound',
  2696. 'value' => $this->config['pingOfflineSound'],
  2697. 'options' => $this->getSounds()
  2698. ),
  2699. array(
  2700. 'type' => 'switch',
  2701. 'name' => 'pingMs',
  2702. 'label' => 'Show Ping Time',
  2703. 'value' => $this->config['pingMs']
  2704. ),
  2705. array(
  2706. 'type' => 'switch',
  2707. 'name' => 'statusSounds',
  2708. 'label' => 'Enable Notify Sounds',
  2709. 'value' => $this->config['statusSounds'],
  2710. 'help' => 'Will play a sound if the server goes down and will play sound if comes back up.',
  2711. ),
  2712. array(
  2713. 'type' => 'select',
  2714. 'name' => 'pingAuthMs',
  2715. 'label' => 'Minimum Authentication for Time Display',
  2716. 'value' => $this->config['pingAuthMs'],
  2717. 'options' => $this->groupSelect()
  2718. ),
  2719. array(
  2720. 'type' => 'select',
  2721. 'name' => 'adminPingRefresh',
  2722. 'label' => 'Admin Refresh Seconds',
  2723. 'value' => $this->config['adminPingRefresh'],
  2724. 'options' => $this->timeOptions()
  2725. ),
  2726. array(
  2727. 'type' => 'select',
  2728. 'name' => 'otherPingRefresh',
  2729. 'label' => 'Everyone Refresh Seconds',
  2730. 'value' => $this->config['otherPingRefresh'],
  2731. 'options' => $this->timeOptions()
  2732. ),
  2733. ),
  2734. 'Certificate' => array(
  2735. array(
  2736. 'type' => 'html',
  2737. 'label' => '',
  2738. 'override' => 12,
  2739. 'html' => '
  2740. <script>
  2741. let myDropzone = new Dropzone("#upload-custom-certificate", {
  2742. url: "api/v2/certificate/custom",
  2743. headers:{ "formKey": local("g","formKey") },
  2744. init: function() {
  2745. this.on("complete", function(file) {
  2746. if(file["status"] === "success"){
  2747. $(".custom-certificate-status").html("<span lang=\"en\">Custom Certificate Loaded</span>");
  2748. }else{
  2749. $(".custom-certificate-status").html("<span lang=\"en\">Error Saving file...</span>");
  2750. }
  2751. });
  2752. }
  2753. });
  2754. </script>
  2755. <div class="row">
  2756. <div class="col-lg-12">
  2757. <div class="panel panel-info">
  2758. <div class="panel-heading"><span lang="en">Notice</span></div>
  2759. <div class="panel-wrapper collapse in" aria-expanded="true">
  2760. <div class="panel-body">
  2761. <span lang="en">By default, Organizr uses certificates from https://curl.se/docs/caextract.html<br/>If you would like to use your own certificate, please upload it below. You will then need to enable each homepage item to use it.</span>
  2762. </div>
  2763. </div>
  2764. </div>
  2765. </div>
  2766. </div>
  2767. <div class="row">
  2768. <div class="col-md-12">
  2769. <div class="white-box">
  2770. <h3 class="box-title m-b-0" lang="en">Custom Certificate Status</h3>
  2771. <p class="text-muted m-b-30 custom-certificate-status">' . $certificateStatus . '</p>
  2772. <form action="#" class="dropzone dz-clickable" id="upload-custom-certificate">
  2773. <div class="dz-default dz-message"><span lang="en">Drop Certificate file here to upload</span></div>
  2774. </form>
  2775. </div>
  2776. </div>
  2777. </div>
  2778. '
  2779. )
  2780. ),
  2781. );
  2782. }
  2783. public function getSettingsSSO()
  2784. {
  2785. return [
  2786. 'FYI' => [
  2787. [
  2788. 'type' => 'html',
  2789. 'label' => '',
  2790. 'override' => 12,
  2791. 'html' => '
  2792. <div class="row">
  2793. <div class="col-lg-12">
  2794. <div class="panel panel-primary">
  2795. <div class="panel-heading"><span lang="en">Please Read First</span></div>
  2796. <div class="panel-wrapper collapse in" aria-expanded="true">
  2797. <div class="panel-body">
  2798. <span lang="en">Using multiple SSO application will cause your Cookie Header item to increase. If you haven\'t increased it by now, please follow this guide</span>
  2799. <br/><br/>
  2800. <div class="row">
  2801. <div class="col-lg-2 col-sm-4 col-xs-12">
  2802. <a href="https://docs.organizr.app/help/faq/organizr-login-error" target="_blank" class="btn btn-block btn-primary text-white" lang="en">Cookie Header Guide</a>
  2803. </div>
  2804. </div>
  2805. <br/>
  2806. <span lang="en">This is not the same as database authentication - i.e. Plex Authentication | Emby Authentication | FTP Authentication<br/>Click Main on the sub-menu above.</span>
  2807. </div>
  2808. </div>
  2809. </div>
  2810. </div>
  2811. </div>
  2812. '
  2813. ]
  2814. ],
  2815. 'Plex' => [
  2816. [
  2817. 'type' => 'password-alt',
  2818. 'name' => 'plexToken',
  2819. 'label' => 'Plex Token',
  2820. 'value' => $this->config['plexToken'],
  2821. 'placeholder' => 'Use Get Token Button'
  2822. ],
  2823. [
  2824. 'type' => 'button',
  2825. 'label' => 'Get Plex Token',
  2826. 'icon' => 'fa fa-ticket',
  2827. 'text' => 'Retrieve',
  2828. 'attr' => 'onclick="PlexOAuth(oAuthSuccess,oAuthError, null, \'#sso-form [name=plexToken]\')"'
  2829. ],
  2830. [
  2831. 'type' => 'password-alt',
  2832. 'name' => 'plexID',
  2833. 'label' => 'Plex Machine',
  2834. 'value' => $this->config['plexID'],
  2835. 'placeholder' => 'Use Get Plex Machine Button'
  2836. ],
  2837. [
  2838. 'type' => 'button',
  2839. 'label' => 'Get Plex Machine',
  2840. 'icon' => 'fa fa-id-badge',
  2841. 'text' => 'Retrieve',
  2842. 'attr' => 'onclick="showPlexMachineForm(\'#sso-form [name=plexID]\')"'
  2843. ],
  2844. [
  2845. 'type' => 'input',
  2846. 'name' => 'plexAdmin',
  2847. 'label' => 'Admin Username',
  2848. 'value' => $this->config['plexAdmin'],
  2849. 'placeholder' => 'Admin username for Plex'
  2850. ],
  2851. [
  2852. 'type' => 'blank',
  2853. 'label' => ''
  2854. ],
  2855. [
  2856. 'type' => 'html',
  2857. 'label' => 'Plex Note',
  2858. 'html' => '<span lang="en">Please make sure both Token and Machine are filled in</span>'
  2859. ],
  2860. [
  2861. 'type' => 'switch',
  2862. 'name' => 'ssoPlex',
  2863. 'label' => 'Enable',
  2864. 'value' => $this->config['ssoPlex']
  2865. ]
  2866. ],
  2867. 'Tautulli' => [
  2868. $this->settingsOption('multiple-url', 'tautulliURL'),
  2869. $this->settingsOption('auth', 'ssoTautulliAuth'),
  2870. $this->settingsOption('enable', 'ssoTautulli'),
  2871. ],
  2872. 'Overseerr' => [
  2873. [
  2874. 'type' => 'input',
  2875. 'name' => 'overseerrURL',
  2876. 'label' => 'Overseerr URL',
  2877. 'value' => $this->config['overseerrURL'],
  2878. 'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
  2879. 'placeholder' => 'http(s)://hostname:port'
  2880. ],
  2881. [
  2882. 'type' => 'password-alt',
  2883. 'name' => 'overseerrToken',
  2884. 'label' => 'Token',
  2885. 'value' => $this->config['overseerrToken']
  2886. ],
  2887. [
  2888. 'type' => 'input',
  2889. 'name' => 'overseerrFallbackUser',
  2890. 'label' => 'Overseerr Fallback Email',
  2891. 'value' => $this->config['overseerrFallbackUser'],
  2892. 'help' => 'DO NOT SET THIS TO YOUR ADMIN ACCOUNT. We recommend you create a local account as a "catch all" for when Organizr is unable to perform SSO. Organizr will request a User Token based off of this user credentials',
  2893. ],
  2894. [
  2895. 'type' => 'password-alt',
  2896. 'name' => 'overseerrFallbackPassword',
  2897. 'label' => 'Overseerr Fallback Password',
  2898. 'value' => $this->config['overseerrFallbackPassword'],
  2899. ],
  2900. [
  2901. 'type' => 'switch',
  2902. 'name' => 'ssoOverseerr',
  2903. 'label' => 'Enable',
  2904. 'value' => $this->config['ssoOverseerr']
  2905. ]
  2906. ],
  2907. 'Petio' => [
  2908. [
  2909. 'type' => 'input',
  2910. 'name' => 'petioURL',
  2911. 'label' => 'Petio URL',
  2912. 'value' => $this->config['petioURL'],
  2913. 'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
  2914. 'placeholder' => 'http(s)://hostname:port'
  2915. ],
  2916. [
  2917. 'type' => 'password-alt',
  2918. 'name' => 'petioToken',
  2919. 'label' => 'Token',
  2920. 'value' => $this->config['petioToken']
  2921. ],
  2922. [
  2923. 'type' => 'input',
  2924. 'name' => 'petioFallbackUser',
  2925. 'label' => 'Petio Fallback User',
  2926. 'value' => $this->config['petioFallbackUser'],
  2927. 'help' => 'DO NOT SET THIS TO YOUR ADMIN ACCOUNT. We recommend you create a local account as a "catch all" for when Organizr is unable to perform SSO. Organizr will request a User Token based off of this user credentials',
  2928. ],
  2929. [
  2930. 'type' => 'password-alt',
  2931. 'name' => 'petioFallbackPassword',
  2932. 'label' => 'Petio Fallback Password',
  2933. 'value' => $this->config['petioFallbackPassword'],
  2934. ],
  2935. [
  2936. 'type' => 'switch',
  2937. 'name' => 'ssoPetio',
  2938. 'label' => 'Enable',
  2939. 'value' => $this->config['ssoPetio']
  2940. ]
  2941. ],
  2942. 'Ombi' => [
  2943. [
  2944. 'type' => 'input',
  2945. 'name' => 'ombiURL',
  2946. 'label' => 'Ombi URL',
  2947. 'value' => $this->config['ombiURL'],
  2948. 'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
  2949. 'placeholder' => 'http(s)://hostname:port'
  2950. ],
  2951. [
  2952. 'type' => 'password-alt',
  2953. 'name' => 'ombiToken',
  2954. 'label' => 'Token',
  2955. 'value' => $this->config['ombiToken']
  2956. ],
  2957. [
  2958. 'type' => 'input',
  2959. 'name' => 'ombiFallbackUser',
  2960. 'label' => 'Ombi Fallback User',
  2961. 'value' => $this->config['ombiFallbackUser'],
  2962. 'help' => 'DO NOT SET THIS TO YOUR ADMIN ACCOUNT. We recommend you create a local account as a "catch all" for when Organizr is unable to perform SSO. Organizr will request a User Token based off of this user credentials'
  2963. ],
  2964. [
  2965. 'type' => 'password-alt',
  2966. 'name' => 'ombiFallbackPassword',
  2967. 'label' => 'Ombi Fallback Password',
  2968. 'value' => $this->config['ombiFallbackPassword']
  2969. ],
  2970. [
  2971. 'type' => 'switch',
  2972. 'name' => 'ssoOmbi',
  2973. 'label' => 'Enable',
  2974. 'value' => $this->config['ssoOmbi']
  2975. ]
  2976. ],
  2977. 'Jellyfin' => [
  2978. [
  2979. 'type' => 'input',
  2980. 'name' => 'jellyfinURL',
  2981. 'label' => 'Jellyfin API URL',
  2982. 'value' => $this->config['jellyfinURL'],
  2983. 'help' => 'Please make sure to use the local address to the API',
  2984. 'placeholder' => 'http(s)://hostname:port'
  2985. ],
  2986. [
  2987. 'type' => 'input',
  2988. 'name' => 'jellyfinSSOURL',
  2989. 'label' => 'Jellyfin SSO URL',
  2990. 'value' => $this->config['jellyfinSSOURL'],
  2991. 'help' => 'Please make sure to use the same (sub)domain to access Jellyfin as Organizr\'s',
  2992. 'placeholder' => 'http(s)://domain.com'
  2993. ],
  2994. [
  2995. 'type' => 'switch',
  2996. 'name' => 'ssoJellyfin',
  2997. 'label' => 'Enable',
  2998. 'value' => $this->config['ssoJellyfin']
  2999. ]
  3000. ],
  3001. 'Komga' => [
  3002. $this->settingsOption('url', 'komgaURL'),
  3003. $this->settingsOption('auth', 'ssoKomgaAuth'),
  3004. $this->settingsOption('enable', 'ssoKomga'),
  3005. ],
  3006. ];
  3007. }
  3008. public function systemMenuLists()
  3009. {
  3010. $userManagementMenu = [
  3011. [
  3012. 'active' => false,
  3013. 'api' => 'api/v2/page/settings_user_manage_users',
  3014. 'anchor' => 'settings-user-manage-users-anchor',
  3015. 'name' => 'Manage Users'
  3016. ],
  3017. [
  3018. 'active' => false,
  3019. 'api' => 'api/v2/page/settings_user_manage_groups',
  3020. 'anchor' => 'settings-user-manage-groups-anchor',
  3021. 'name' => 'Manage Groups'
  3022. ],
  3023. [
  3024. 'active' => false,
  3025. 'api' => false,
  3026. 'anchor' => 'settings-user-import-users-anchor',
  3027. 'name' => 'Import Users'
  3028. ],
  3029. ];
  3030. $customizeMenu = [
  3031. [
  3032. 'active' => false,
  3033. 'api' => 'api/v2/page/settings_customize_appearance',
  3034. 'anchor' => 'settings-customize-appearance-anchor',
  3035. 'name' => 'Appearance',
  3036. ],
  3037. [
  3038. 'active' => false,
  3039. 'api' => false,
  3040. 'anchor' => 'settings-customize-marketplace-anchor',
  3041. 'name' => 'Marketplace',
  3042. 'onclick' => 'loadMarketplace(\'themes\');'
  3043. ],
  3044. ];
  3045. $tabEditorMenu = [
  3046. [
  3047. 'active' => false,
  3048. 'api' => 'api/v2/page/settings_tab_editor_tabs',
  3049. 'anchor' => 'settings-tab-editor-tabs-anchor',
  3050. 'name' => 'Tabs'
  3051. ],
  3052. [
  3053. 'active' => false,
  3054. 'api' => 'api/v2/page/settings_tab_editor_categories',
  3055. 'anchor' => 'settings-tab-editor-categories-anchor',
  3056. 'name' => 'Categories'
  3057. ],
  3058. [
  3059. 'active' => false,
  3060. 'api' => 'api/v2/page/settings_tab_editor_homepage',
  3061. 'anchor' => 'settings-tab-editor-homepage-anchor',
  3062. 'name' => 'Homepage Items'
  3063. ],
  3064. [
  3065. 'active' => false,
  3066. 'api' => 'api/v2/page/settings_tab_editor_homepage_order',
  3067. 'anchor' => 'settings-tab-editor-homepage-order-anchor',
  3068. 'name' => 'Homepage Order'
  3069. ],
  3070. ];
  3071. $systemSettingsMenu = [
  3072. [
  3073. 'active' => true,
  3074. 'api' => false,
  3075. 'anchor' => 'settings-settings-about-anchor',
  3076. 'name' => 'About'
  3077. ],
  3078. [
  3079. 'active' => false,
  3080. 'api' => 'api/v2/page/settings_settings_main',
  3081. 'anchor' => 'settings-settings-main-anchor',
  3082. 'name' => 'Main'
  3083. ],
  3084. [
  3085. 'active' => false,
  3086. 'api' => 'api/v2/page/settings_settings_sso',
  3087. 'anchor' => 'settings-settings-sso-anchor',
  3088. 'name' => 'SSO'
  3089. ],
  3090. [
  3091. 'active' => false,
  3092. 'api' => 'api/v2/page/settings_settings_logs',
  3093. 'anchor' => 'settings-settings-logs-anchor',
  3094. 'name' => 'Logs'
  3095. ],
  3096. [
  3097. 'active' => false,
  3098. 'api' => false,
  3099. 'anchor' => 'settings-settings-updates-anchor',
  3100. 'name' => 'Updates'
  3101. ],
  3102. [
  3103. 'active' => false,
  3104. 'api' => 'api/v2/page/settings_settings_backup',
  3105. 'anchor' => 'settings-settings-backup-anchor',
  3106. 'name' => 'Backup'
  3107. ],
  3108. [
  3109. 'active' => false,
  3110. 'api' => false,
  3111. 'anchor' => 'settings-settings-donate-anchor',
  3112. 'name' => 'Donate'
  3113. ],
  3114. ];
  3115. $systemMenus['system_settings'] = $this->buildSettingsMenus($systemSettingsMenu, 'System Settings');
  3116. $systemMenus['tab_editor'] = $this->buildSettingsMenus($tabEditorMenu, 'Tab Editor');
  3117. $systemMenus['customize'] = $this->buildSettingsMenus($customizeMenu, 'Customize');
  3118. $systemMenus['user_management'] = $this->buildSettingsMenus($userManagementMenu, 'User Management');
  3119. return $systemMenus;
  3120. }
  3121. public function updateConfigMultiple($array)
  3122. {
  3123. return (bool)$this->updateConfig($array);
  3124. }
  3125. public function updateConfigItems($array)
  3126. {
  3127. if (!count($array)) {
  3128. $this->setAPIResponse('error', 'No data submitted', 409);
  3129. return false;
  3130. }
  3131. $newItem = array();
  3132. foreach ($array as $k => $v) {
  3133. $v = $v ?? '';
  3134. switch ($v) {
  3135. case 'true':
  3136. $v = (bool)true;
  3137. break;
  3138. case 'false':
  3139. $v = (bool)false;
  3140. break;
  3141. }
  3142. // Hash
  3143. if ((stripos($k, 'password') !== false)) {
  3144. if (!$this->isEncrypted($v)) {
  3145. if ($v !== '') {
  3146. $v = $this->encrypt($v);
  3147. }
  3148. }
  3149. }
  3150. if (strtolower($k) !== 'formkey') {
  3151. $newItem[$k] = $v;
  3152. $this->config[$k] = $v;
  3153. }
  3154. }
  3155. $this->setAPIResponse('success', 'Config items updated', 200);
  3156. return (bool)$this->updateConfig($newItem);
  3157. }
  3158. public function updateConfigItem($array)
  3159. {
  3160. $array['value'] = $array['value'] ?? '';
  3161. switch ($array['value']) {
  3162. case 'true':
  3163. $array['value'] = (bool)true;
  3164. break;
  3165. case 'false':
  3166. $array['value'] = (bool)false;
  3167. break;
  3168. }
  3169. // Hash
  3170. if ($array['type'] == 'password') {
  3171. $array['value'] = $this->encrypt($array['value']);
  3172. }
  3173. $newItem = array(
  3174. $array['name'] => $array['value']
  3175. );
  3176. $this->config[$array['name']] = $array['value'];
  3177. return (bool)$this->updateConfig($newItem);
  3178. }
  3179. public function ignoreNewsId($id)
  3180. {
  3181. if (!$id) {
  3182. $this->setAPIResponse('error', 'News id was not supplied', 409);
  3183. return false;
  3184. }
  3185. $id = array(intval($id));
  3186. $newsIds = $this->config['ignoredNewsIds'];
  3187. $newsIds = array_merge($newsIds, $id);
  3188. $newsIds = array_unique($newsIds);
  3189. $this->updateConfig(['ignoredNewsIds' => $newsIds]);
  3190. $this->setAPIResponse('success', 'News id is now ignored', 200, null);
  3191. }
  3192. public function getNewsIds()
  3193. {
  3194. $newsIds = $this->config['ignoredNewsIds'];
  3195. $this->setAPIResponse('success', null, 200, $newsIds);
  3196. return $newsIds;
  3197. }
  3198. public function testWizardPath($array)
  3199. {
  3200. if ($this->hasDB()) {
  3201. $this->setAPIResponse('error', 'Endpoint disabled as database already exists', 401);
  3202. return false;
  3203. }
  3204. $path = $array['path'] ?? null;
  3205. if (file_exists($path)) {
  3206. if (is_writable($path)) {
  3207. $this->setAPIResponse('success', 'Path exists and is writable', 200);
  3208. return true;
  3209. }
  3210. } else {
  3211. if (is_writable(dirname($path, 1))) {
  3212. if (mkdir($path, 0760, true)) {
  3213. $this->setAPIResponse('success', 'Path is writable - Creating now', 200);
  3214. return true;
  3215. }
  3216. }
  3217. }
  3218. $this->setAPIResponse('error', 'Path is not writable', 401);
  3219. return false;
  3220. }
  3221. public function wizardConfig($array)
  3222. {
  3223. $dbName = $array['dbName'] ?? null;
  3224. $path = $array['dbPath'] ?? null;
  3225. $license = $array['license'] ?? null;
  3226. $hashKey = $array['hashKey'] ?? null;
  3227. $api = $array['api'] ?? null;
  3228. $registrationPassword = $array['registrationPassword'] ?? null;
  3229. $username = $array['username'] ?? null;
  3230. $password = $array['password'] ?? null;
  3231. $email = $array['email'] ?? null;
  3232. $validation = array(
  3233. 'dbName' => $dbName,
  3234. 'dbPath' => $path,
  3235. 'license' => $license,
  3236. 'hashKey' => $hashKey,
  3237. 'api' => $api,
  3238. 'registrationPassword' => $registrationPassword,
  3239. 'username' => $username,
  3240. 'password' => $password,
  3241. 'email' => $email,
  3242. );
  3243. foreach ($validation as $k => $v) {
  3244. if ($v == null) {
  3245. $this->setAPIResponse('error', '[' . $k . '] cannot be empty', 422);
  3246. return false;
  3247. }
  3248. }
  3249. $path = $this->cleanDirectory($path);
  3250. if (file_exists($path)) {
  3251. if (!is_writable($path)) {
  3252. $this->setAPIResponse('error', '[' . $path . '] is not writable', 422);
  3253. return false;
  3254. }
  3255. } else {
  3256. if (is_writable(dirname($path, 1))) {
  3257. if (!mkdir($path, 0760, true)) {
  3258. $this->setAPIResponse('error', '[' . $path . '] is not writable', 422);
  3259. return false;
  3260. }
  3261. } else {
  3262. $this->setAPIResponse('error', '[' . $path . '] is not writable', 422);
  3263. return false;
  3264. }
  3265. }
  3266. $dbName = $this->dbExtension($dbName);
  3267. $configVersion = $this->version;
  3268. $configArray = array(
  3269. 'dbName' => $dbName,
  3270. 'dbLocation' => $path,
  3271. 'license' => $license,
  3272. 'organizrHash' => $hashKey,
  3273. 'organizrAPI' => $api,
  3274. 'registrationPassword' => $registrationPassword,
  3275. 'uuid' => $this->gen_uuid()
  3276. );
  3277. // Create Config
  3278. if ($this->createConfig($configArray)) {
  3279. $this->config = $this->config();
  3280. $this->refreshCookieName();
  3281. $this->connectDB();
  3282. // Call DB Create
  3283. if ($this->createDB($path)) {
  3284. // Add in first user
  3285. if ($this->createFirstAdmin($username, $password, $email)) {
  3286. if ($this->createToken($username, $email, 1)) {
  3287. return true;
  3288. } else {
  3289. $this->setAPIResponse('error', 'error creating token', 500);
  3290. }
  3291. } else {
  3292. $this->setAPIResponse('error', 'error creating admin', 500);
  3293. }
  3294. } else {
  3295. $this->setAPIResponse('error', 'error creating database', 500);
  3296. }
  3297. } else {
  3298. $this->setAPIResponse('error', 'error creating config', 500);
  3299. }
  3300. return false;
  3301. }
  3302. public function createDB($path, $migration = false)
  3303. {
  3304. if (!file_exists($path)) {
  3305. mkdir($path, 0777, true);
  3306. }
  3307. $response = [
  3308. array(
  3309. 'function' => 'query',
  3310. 'query' => 'CREATE TABLE `users` (
  3311. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3312. `username` TEXT UNIQUE,
  3313. `password` TEXT,
  3314. `email` TEXT,
  3315. `plex_token` TEXT,
  3316. `group` TEXT,
  3317. `group_id` INTEGER,
  3318. `locked` INTEGER,
  3319. `image` TEXT,
  3320. `register_date` DATE,
  3321. `auth_service` TEXT DEFAULT \'internal\'
  3322. );'
  3323. ),
  3324. array(
  3325. 'function' => 'query',
  3326. 'query' => 'CREATE TABLE `chatroom` (
  3327. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3328. `username` TEXT,
  3329. `gravatar` TEXT,
  3330. `uid` TEXT,
  3331. `date` DATE,
  3332. `ip` TEXT,
  3333. `message` TEXT
  3334. );'
  3335. ),
  3336. array(
  3337. 'function' => 'query',
  3338. 'query' => 'CREATE TABLE `tokens` (
  3339. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3340. `token` TEXT UNIQUE,
  3341. `user_id` INTEGER,
  3342. `browser` TEXT,
  3343. `ip` TEXT,
  3344. `created` DATE,
  3345. `expires` DATE
  3346. );'
  3347. ),
  3348. array(
  3349. 'function' => 'query',
  3350. 'query' => 'CREATE TABLE `groups` (
  3351. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3352. `group` TEXT UNIQUE,
  3353. `group_id` INTEGER,
  3354. `image` TEXT,
  3355. `default` INTEGER
  3356. );'
  3357. ),
  3358. array(
  3359. 'function' => 'query',
  3360. 'query' => 'CREATE TABLE `categories` (
  3361. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3362. `order` INTEGER,
  3363. `category` TEXT UNIQUE,
  3364. `category_id` INTEGER,
  3365. `image` TEXT,
  3366. `default` INTEGER
  3367. );'
  3368. ),
  3369. array(
  3370. 'function' => 'query',
  3371. 'query' => 'CREATE TABLE `tabs` (
  3372. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3373. `order` INTEGER,
  3374. `category_id` INTEGER,
  3375. `name` TEXT,
  3376. `url` TEXT,
  3377. `url_local` TEXT,
  3378. `default` INTEGER,
  3379. `enabled` INTEGER,
  3380. `group_id` INTEGER,
  3381. `image` TEXT,
  3382. `type` INTEGER,
  3383. `splash` INTEGER,
  3384. `ping` INTEGER,
  3385. `ping_url` TEXT,
  3386. `timeout` INTEGER,
  3387. `timeout_ms` INTEGER,
  3388. `preload` INTEGER
  3389. );'
  3390. ),
  3391. array(
  3392. 'function' => 'query',
  3393. 'query' => 'CREATE TABLE `options` (
  3394. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3395. `name` TEXT UNIQUE,
  3396. `value` TEXT
  3397. );'
  3398. ),
  3399. array(
  3400. 'function' => 'query',
  3401. 'query' => 'CREATE TABLE `invites` (
  3402. `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
  3403. `code` TEXT UNIQUE,
  3404. `date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  3405. `email` TEXT,
  3406. `username` TEXT,
  3407. `dateused` TIMESTAMP,
  3408. `usedby` TEXT,
  3409. `ip` TEXT,
  3410. `valid` TEXT,
  3411. `type` TEXT
  3412. );'
  3413. ),
  3414. ];
  3415. return $this->processQueries($response, $migration);
  3416. }
  3417. public function createFirstAdmin($username, $password, $email)
  3418. {
  3419. $userInfo = [
  3420. 'username' => $username,
  3421. 'password' => password_hash($password, PASSWORD_BCRYPT),
  3422. 'email' => $email,
  3423. 'group' => 'Admin',
  3424. 'group_id' => 0,
  3425. 'image' => $this->gravatar($email),
  3426. 'register_date' => $this->currentTime,
  3427. ];
  3428. $groupInfo0 = [
  3429. 'group' => 'Admin',
  3430. 'group_id' => 0,
  3431. 'default' => false,
  3432. 'image' => 'plugins/images/groups/admin.png',
  3433. ];
  3434. $groupInfo1 = [
  3435. 'group' => 'Co-Admin',
  3436. 'group_id' => 1,
  3437. 'default' => false,
  3438. 'image' => 'plugins/images/groups/coadmin.png',
  3439. ];
  3440. $groupInfo2 = [
  3441. 'group' => 'Super User',
  3442. 'group_id' => 2,
  3443. 'default' => false,
  3444. 'image' => 'plugins/images/groups/superuser.png',
  3445. ];
  3446. $groupInfo3 = [
  3447. 'group' => 'Power User',
  3448. 'group_id' => 3,
  3449. 'default' => false,
  3450. 'image' => 'plugins/images/groups/poweruser.png',
  3451. ];
  3452. $groupInfo4 = [
  3453. 'group' => 'User',
  3454. 'group_id' => 4,
  3455. 'default' => true,
  3456. 'image' => 'plugins/images/groups/user.png',
  3457. ];
  3458. $groupInfoGuest = [
  3459. 'group' => 'Guest',
  3460. 'group_id' => 999,
  3461. 'default' => false,
  3462. 'image' => 'plugins/images/groups/guest.png',
  3463. ];
  3464. $settingsInfo = [
  3465. 'order' => 1,
  3466. 'category_id' => 0,
  3467. 'name' => 'Settings',
  3468. 'url' => 'api/v2/page/settings',
  3469. 'default' => false,
  3470. 'enabled' => true,
  3471. 'group_id' => 1,
  3472. 'image' => 'fontawesome::cog',
  3473. 'type' => 0
  3474. ];
  3475. $homepageInfo = [
  3476. 'order' => 2,
  3477. 'category_id' => 0,
  3478. 'name' => 'Homepage',
  3479. 'url' => 'api/v2/page/homepage',
  3480. 'default' => false,
  3481. 'enabled' => false,
  3482. 'group_id' => 4,
  3483. 'image' => 'fontawesome::home',
  3484. 'type' => 0
  3485. ];
  3486. $unsortedInfo = [
  3487. 'order' => 1,
  3488. 'category' => 'Unsorted',
  3489. 'category_id' => 0,
  3490. 'image' => 'fontawesome::question',
  3491. 'default' => true
  3492. ];
  3493. $response = [
  3494. array(
  3495. 'function' => 'query',
  3496. 'query' => array(
  3497. 'INSERT INTO [users]',
  3498. $userInfo
  3499. )
  3500. ),
  3501. array(
  3502. 'function' => 'query',
  3503. 'query' => array(
  3504. 'INSERT INTO [groups]',
  3505. $groupInfo0
  3506. )
  3507. ),
  3508. array(
  3509. 'function' => 'query',
  3510. 'query' => array(
  3511. 'INSERT INTO [groups]',
  3512. $groupInfo1
  3513. )
  3514. ),
  3515. array(
  3516. 'function' => 'query',
  3517. 'query' => array(
  3518. 'INSERT INTO [groups]',
  3519. $groupInfo2
  3520. )
  3521. ),
  3522. array(
  3523. 'function' => 'query',
  3524. 'query' => array(
  3525. 'INSERT INTO [groups]',
  3526. $groupInfo3
  3527. )
  3528. ),
  3529. array(
  3530. 'function' => 'query',
  3531. 'query' => array(
  3532. 'INSERT INTO [groups]',
  3533. $groupInfo4
  3534. )
  3535. ),
  3536. array(
  3537. 'function' => 'query',
  3538. 'query' => array(
  3539. 'INSERT INTO [groups]',
  3540. $groupInfoGuest
  3541. )
  3542. ),
  3543. array(
  3544. 'function' => 'query',
  3545. 'query' => array(
  3546. 'INSERT INTO [tabs]',
  3547. $settingsInfo
  3548. )
  3549. ),
  3550. array(
  3551. 'function' => 'query',
  3552. 'query' => array(
  3553. 'INSERT INTO [tabs]',
  3554. $homepageInfo
  3555. )
  3556. ),
  3557. array(
  3558. 'function' => 'query',
  3559. 'query' => array(
  3560. 'INSERT INTO [categories]',
  3561. $unsortedInfo
  3562. )
  3563. ),
  3564. ];
  3565. return $this->processQueries($response);
  3566. }
  3567. public function getUserByUsernameAndEmail($username, $email)
  3568. {
  3569. $response = [
  3570. array(
  3571. 'function' => 'fetch',
  3572. 'query' => array(
  3573. 'SELECT * FROM users WHERE username = ? COLLATE NOCASE OR email = ? COLLATE NOCASE',
  3574. [$username],
  3575. [$email]
  3576. )
  3577. ),
  3578. ];
  3579. return $this->processQueries($response);
  3580. }
  3581. public function createToken($username, $email, $days = 1)
  3582. {
  3583. $days = ($days > 365) ? 365 : $days;
  3584. //Quick get user ID
  3585. $result = $this->getUserByUsernameAndEmail($username, $email);
  3586. // Create JWT
  3587. // Set key
  3588. // SHA256 Encryption
  3589. $signer = new Lcobucci\JWT\Signer\Hmac\Sha256();
  3590. // Start Builder
  3591. $jwttoken = (new Lcobucci\JWT\Builder())->issuedBy('Organizr')// Configures the issuer (iss claim)
  3592. ->permittedFor('Organizr')// Configures the audience (aud claim)
  3593. ->identifiedBy('4f1g23a12aa', true)// Configures the id (jti claim), replicating as a header item
  3594. ->issuedAt(time())// Configures the time that the token was issue (iat claim)
  3595. ->expiresAt(time() + (86400 * $days))// Configures the expiration time of the token (exp claim)
  3596. ->withClaim('name', $result['username'])// Configures a new claim, called "name"
  3597. ->withClaim('group', $result['group'])// Configures a new claim, called "group"
  3598. ->withClaim('groupID', $result['group_id'])// Configures a new claim, called "groupID"
  3599. ->withClaim('email', $result['email'])// Configures a new claim, called "email"
  3600. ->withClaim('image', $result['image'])// Configures a new claim, called "image"
  3601. ->withClaim('userID', $result['id'])// Configures a new claim, called "image"
  3602. ->sign($signer, $this->config['organizrHash'])// creates a signature using "testing" as key
  3603. ->getToken(); // Retrieves the generated token
  3604. $jwttoken->getHeaders(); // Retrieves the token headers
  3605. $jwttoken->getClaims(); // Retrieves the token claims
  3606. $this->coookie('set', $this->cookieName, $jwttoken, $days);
  3607. // Add token to DB
  3608. $addToken = [
  3609. 'token' => (string)$jwttoken,
  3610. 'user_id' => $result['id'],
  3611. 'created' => $this->currentTime,
  3612. 'browser' => isset($_SERVER ['HTTP_USER_AGENT']) ? $_SERVER ['HTTP_USER_AGENT'] : null,
  3613. 'ip' => $this->userIP(),
  3614. 'expires' => gmdate("Y-m-d\TH:i:s\Z", time() + (86400 * $days))
  3615. ];
  3616. $response = [
  3617. array(
  3618. 'function' => 'query',
  3619. 'query' => array(
  3620. 'INSERT INTO [tokens]',
  3621. $addToken
  3622. )
  3623. ),
  3624. ];
  3625. $token = $this->processQueries($response);
  3626. return $jwttoken;
  3627. }
  3628. public function login($array)
  3629. {
  3630. // Grab username, Password & other optional items from api call
  3631. $username = $array['username'] ?? null;
  3632. $password = $array['password'] ?? null;
  3633. $oAuth = $array['oAuth'] ?? null;
  3634. $oAuthType = $array['oAuthType'] ?? null;
  3635. $remember = $array['remember'] ?? null;
  3636. $tfaCode = $array['tfaCode'] ?? null;
  3637. $loginAttempts = $array['loginAttempts'] ?? null;
  3638. $output = $array['output'] ?? null;
  3639. $username = (strpos($this->config['authBackend'], 'emby') !== false) ? $username : strtolower($username);
  3640. $days = (isset($remember)) ? $this->config['rememberMeDays'] : 1;
  3641. // Set other variables
  3642. $function = 'plugin_auth_' . $this->config['authBackend'];
  3643. $authSuccess = false;
  3644. $authProxy = false;
  3645. $addEmailToAuthProxy = true;
  3646. // Check Login attempts and kill if over limit
  3647. if ($loginAttempts > $this->config['loginAttempts'] || isset($_COOKIE['lockout'])) {
  3648. $this->coookieSeconds('set', 'lockout', $this->config['loginLockout'], $this->config['loginLockout']);
  3649. $this->setAPIResponse('error', 'User is locked out', 403);
  3650. return false;
  3651. }
  3652. // Check if Auth Proxy is enabled
  3653. if ($this->config['authProxyEnabled'] && $this->config['authProxyHeaderName'] !== '' && $this->config['authProxyWhitelist'] !== '') {
  3654. if (isset($this->getallheaders()[$this->config['authProxyHeaderName']])) {
  3655. $usernameHeader = $this->getallheaders()[$this->config['authProxyHeaderName']] ?? $username;
  3656. $emailHeader = $this->getallheaders()[$this->config['authProxyHeaderNameEmail']] ?? null;
  3657. $this->writeLog('success', 'Auth Proxy Function - Starting Verification for IP: ' . $this->userIP() . ' for request on: ' . $_SERVER['REMOTE_ADDR'] . ' against IP/Subnet: ' . $this->config['authProxyWhitelist'], $usernameHeader);
  3658. $whitelistRange = $this->analyzeIP($this->config['authProxyWhitelist']);
  3659. $authProxy = $this->authProxyRangeCheck($whitelistRange['from'], $whitelistRange['to']);
  3660. $username = ($authProxy) ? $usernameHeader : $username;
  3661. $password = ($password == null) ? $this->random_ascii_string(10) : $password;
  3662. $addEmailToAuthProxy = ($authProxy && $emailHeader) ? ['email' => $emailHeader] : true;
  3663. if ($authProxy) {
  3664. $this->writeLog('success', 'Auth Proxy Function - IP: ' . $this->userIP() . ' has been verified', $usernameHeader);
  3665. } else {
  3666. $this->writeLog('error', 'Auth Proxy Function - IP: ' . $this->userIP() . ' has failed verification', $usernameHeader);
  3667. }
  3668. }
  3669. }
  3670. // Check if Login method was an oAuth login
  3671. if (!$oAuth) {
  3672. $result = $this->getUserByUsernameAndEmail($username, $username);
  3673. $result['password'] = $result['password'] ?? '';
  3674. // Switch AuthType - internal - external - both
  3675. switch ($this->config['authType']) {
  3676. case 'external':
  3677. if (method_exists($this, $function)) {
  3678. $authSuccess = $this->$function($username, $password);
  3679. }
  3680. break;
  3681. /** @noinspection PhpMissingBreakStatementInspection */
  3682. case 'both':
  3683. if (method_exists($this, $function)) {
  3684. $authSuccess = $this->$function($username, $password);
  3685. }
  3686. // no break
  3687. default: // Internal
  3688. if (!$authSuccess) {
  3689. // perform the internal authentication step
  3690. if (password_verify($password, $result['password'])) {
  3691. $authSuccess = true;
  3692. }
  3693. }
  3694. }
  3695. $authSuccess = ($authProxy) ? $addEmailToAuthProxy : $authSuccess;
  3696. } else {
  3697. // Has oAuth Token!
  3698. switch ($oAuthType) {
  3699. case 'plex':
  3700. if ($this->config['plexoAuth']) {
  3701. $tokenInfo = $this->checkPlexToken($oAuth);
  3702. if ($tokenInfo) {
  3703. $authSuccess = array(
  3704. 'username' => $tokenInfo['user']['username'],
  3705. 'email' => $tokenInfo['user']['email'],
  3706. 'image' => $tokenInfo['user']['thumb'],
  3707. 'token' => $tokenInfo['user']['authToken'],
  3708. 'oauth' => 'plex'
  3709. );
  3710. $this->coookie('set', 'oAuth', 'true', $this->config['rememberMeDays']);
  3711. $authSuccess = ((!empty($this->config['plexAdmin']) && strtolower($this->config['plexAdmin']) == strtolower($tokenInfo['user']['username'])) || (!empty($this->config['plexAdmin']) && strtolower($this->config['plexAdmin']) == strtolower($tokenInfo['user']['email'])) || $this->checkPlexUser($tokenInfo['user']['username'])) ? $authSuccess : false;
  3712. }
  3713. } else {
  3714. $this->setAPIResponse('error', 'Plex oAuth is not setup', 422);
  3715. return false;
  3716. }
  3717. break;
  3718. default:
  3719. return ($output) ? 'No oAuthType defined' : 'error';
  3720. }
  3721. $result = ($authSuccess) ? $this->getUserByUsernameAndEmail($authSuccess['username'], $authSuccess['email']) : '';
  3722. }
  3723. if ($authSuccess) {
  3724. // Make sure user exists in database
  3725. $userExists = false;
  3726. $passwordMatches = $oAuth || $authProxy;
  3727. $token = (is_array($authSuccess) && isset($authSuccess['token']) ? $authSuccess['token'] : '');
  3728. if (isset($result['username'])) {
  3729. $userExists = true;
  3730. $username = $result['username'];
  3731. if ($passwordMatches == false) {
  3732. $passwordMatches = password_verify($password, $result['password']);
  3733. }
  3734. }
  3735. if ($userExists) {
  3736. //does org password need to be updated
  3737. if (!$passwordMatches) {
  3738. $this->updateUserPassword($password, $result['id']);
  3739. $this->writeLog('success', 'Login Function - User Password updated from backend', $username);
  3740. }
  3741. if ($token !== '') {
  3742. if ($token !== $result['plex_token']) {
  3743. $this->updateUserPlexToken($token, $result['id']);
  3744. $this->writeLog('success', 'Login Function - User Plex Token updated from backend', $username);
  3745. }
  3746. }
  3747. // 2FA might go here
  3748. if ($result['auth_service'] !== 'internal' && strpos($result['auth_service'], '::') !== false) {
  3749. $tfaProceed = true;
  3750. // Add check for local or not
  3751. if ($this->config['ignoreTFALocal'] !== false) {
  3752. $tfaProceed = !$this->isLocal();
  3753. }
  3754. // Is Plex Oauth?
  3755. if ($this->config['ignoreTFAIfPlexOAuth'] !== false) {
  3756. if (isset($authSuccess['oauth'])) {
  3757. if ($authSuccess['oauth'] == 'plex') {
  3758. $tfaProceed = false;
  3759. }
  3760. }
  3761. }
  3762. if ($tfaProceed) {
  3763. $TFA = explode('::', $result['auth_service']);
  3764. // Is code with login info?
  3765. if ($tfaCode == '') {
  3766. $this->setAPIResponse('warning', '2FA Code Needed', 422);
  3767. return false;
  3768. } else {
  3769. if (!$this->verify2FA($TFA[1], $tfaCode, $TFA[0])) {
  3770. $this->writeLoginLog($username, 'error');
  3771. $this->writeLog('error', 'Login Function - Wrong 2FA', $username);
  3772. $this->setAPIResponse('error', 'Wrong 2FA', 422);
  3773. return false;
  3774. }
  3775. }
  3776. }
  3777. }
  3778. // End 2FA
  3779. // authentication passed - 1) mark active and update token
  3780. $createToken = $this->createToken($result['username'], $result['email'], $days);
  3781. if ($createToken) {
  3782. $this->writeLoginLog($username, 'success');
  3783. $this->writeLog('success', 'Login Function - A User has logged in', $username);
  3784. $this->ssoCheck($result, $password, $token); //need to work on this
  3785. return ($output) ? array('name' => $this->cookieName, 'token' => (string)$createToken) : true;
  3786. } else {
  3787. $this->setAPIResponse('error', 'Token creation error', 500);
  3788. return false;
  3789. }
  3790. } else {
  3791. // Create User
  3792. return $this->authRegister((is_array($authSuccess) && isset($authSuccess['username']) ? $authSuccess['username'] : $username), $password, (is_array($authSuccess) && isset($authSuccess['email']) ? $authSuccess['email'] : ''), $token);
  3793. }
  3794. } else {
  3795. // authentication failed
  3796. $this->writeLoginLog($username, 'error');
  3797. $this->writeLog('error', 'Login Function - Wrong Password', $username);
  3798. if ($loginAttempts >= $this->config['loginAttempts']) {
  3799. $this->coookieSeconds('set', 'lockout', $this->config['loginLockout'], $this->config['loginLockout']);
  3800. $this->setAPIResponse('error', 'User is locked out', 403);
  3801. return false;
  3802. } else {
  3803. $this->setAPIResponse('error', 'User credentials incorrect', 401);
  3804. return false;
  3805. }
  3806. }
  3807. }
  3808. public function logout()
  3809. {
  3810. $this->coookie('delete', $this->cookieName);
  3811. $this->coookie('delete', 'mpt');
  3812. $this->coookie('delete', 'Auth');
  3813. $this->coookie('delete', 'oAuth');
  3814. $this->coookie('delete', 'connect.sid');
  3815. $this->coookie('delete', 'petio_jwt');
  3816. $this->clearTautulliTokens();
  3817. $this->clearJellyfinTokens();
  3818. $this->revokeTokenCurrentUser($this->user['token']);
  3819. $this->clearKomgaToken();
  3820. $this->user = null;
  3821. return true;
  3822. }
  3823. public function recover($array)
  3824. {
  3825. $email = $array['email'] ?? null;
  3826. if (!$email) {
  3827. $this->setAPIResponse('error', 'Email was not supplied', 422);
  3828. return false;
  3829. }
  3830. $newPassword = $this->randString(10);
  3831. $isUser = $this->getUserByEmail($email);
  3832. if ($isUser) {
  3833. $this->updateUserPassword($newPassword, $isUser['id']);
  3834. $this->setAPIResponse('success', 'User password has been reset', 200);
  3835. $this->writeLog('success', 'User Management Function - User: ' . $isUser['username'] . '\'s password was reset', $isUser['username']);
  3836. if ($this->config['PHPMAILER-enabled']) {
  3837. $PhpMailer = new PhpMailer();
  3838. $emailTemplate = array(
  3839. 'type' => 'reset',
  3840. 'body' => $this->config['PHPMAILER-emailTemplateReset'],
  3841. 'subject' => $this->config['PHPMAILER-emailTemplateResetSubject'],
  3842. 'user' => $isUser['username'],
  3843. 'password' => $newPassword,
  3844. 'inviteCode' => null,
  3845. );
  3846. $emailTemplate = $PhpMailer->_phpMailerPluginEmailTemplate($emailTemplate);
  3847. $sendEmail = array(
  3848. 'to' => $email,
  3849. 'user' => $isUser['username'],
  3850. 'subject' => $emailTemplate['subject'],
  3851. 'body' => $PhpMailer->_phpMailerPluginBuildEmail($emailTemplate),
  3852. );
  3853. $PhpMailer->_phpMailerPluginSendEmail($sendEmail);
  3854. $this->setAPIResponse('success', 'User password has been reset and email has been sent', 200);
  3855. }
  3856. return true;
  3857. } else {
  3858. $this->setAPIResponse('error', 'User not found', 404);
  3859. return false;
  3860. }
  3861. }
  3862. public function register($array)
  3863. {
  3864. $email = $array['email'] ?? null;
  3865. $username = $array['username'] ?? null;
  3866. $password = $array['password'] ?? null;
  3867. $registrationPassword = $array['registrationPassword'] ?? null;
  3868. if (!$email) {
  3869. $this->setAPIResponse('error', 'Email was not supplied', 422);
  3870. return false;
  3871. }
  3872. if (!$username) {
  3873. $this->setAPIResponse('error', 'Username was not supplied', 422);
  3874. return false;
  3875. }
  3876. if (!$password) {
  3877. $this->setAPIResponse('error', 'Password was not supplied', 422);
  3878. return false;
  3879. }
  3880. if (!$registrationPassword) {
  3881. $this->setAPIResponse('error', 'Registration Password was not supplied', 422);
  3882. return false;
  3883. }
  3884. if ($registrationPassword == $this->decrypt($this->config['registrationPassword'])) {
  3885. $this->writeLog('success', 'Registration Function - Registration Password Verified', $username);
  3886. if ($this->createUser($username, $password, $email)) {
  3887. $this->writeLog('success', 'Registration Function - A User has registered', $username);
  3888. if ($this->createToken($username, $email, $this->config['rememberMeDays'])) {
  3889. $this->writeLoginLog($username, 'success');
  3890. $this->writeLog('success', 'Login Function - A User has logged in', $username);
  3891. return true;
  3892. }
  3893. } else {
  3894. return false;
  3895. }
  3896. } else {
  3897. $this->writeLog('warning', 'Registration Function - Wrong Password', $username);
  3898. $this->setAPIResponse('error', 'Registration Password was incorrect', 401);
  3899. return false;
  3900. }
  3901. }
  3902. public function authRegister($username, $password, $email, $token = null)
  3903. {
  3904. if ($this->config['authBackend'] !== '') {
  3905. $this->ombiImport($this->config['authBackend']);
  3906. }
  3907. $this->ssoCheck($username, $password, $token);
  3908. if ($token && (!$password || $password == '')) {
  3909. $password = $this->random_ascii_string(10);
  3910. }
  3911. if ($this->createUser($username, $password, $email)) {
  3912. $this->writeLog('success', 'Registration Function - A User has registered', $username);
  3913. if ($this->config['PHPMAILER-enabled'] && $email !== '') {
  3914. $PhpMailer = new PhpMailer();
  3915. $emailTemplate = array(
  3916. 'type' => 'registration',
  3917. 'body' => $this->config['PHPMAILER-emailTemplateRegisterUser'],
  3918. 'subject' => $this->config['PHPMAILER-emailTemplateRegisterUserSubject'],
  3919. 'user' => $username,
  3920. 'password' => null,
  3921. 'inviteCode' => null,
  3922. );
  3923. $emailTemplate = $PhpMailer->_phpMailerPluginEmailTemplate($emailTemplate);
  3924. $sendEmail = array(
  3925. 'to' => $email,
  3926. 'user' => $username,
  3927. 'subject' => $emailTemplate['subject'],
  3928. 'body' => $PhpMailer->_phpMailerPluginBuildEmail($emailTemplate),
  3929. );
  3930. $PhpMailer->_phpMailerPluginSendEmail($sendEmail);
  3931. }
  3932. if ($this->createToken($username, $email, $this->config['rememberMeDays'])) {
  3933. $this->writeLoginLog($username, 'success');
  3934. $this->writeLog('success', 'Login Function - A User has logged in', $username);
  3935. return true;
  3936. } else {
  3937. return false;
  3938. }
  3939. } else {
  3940. $this->writeLog('error', 'Registration Function - An error occurred', $username);
  3941. return false;
  3942. }
  3943. }
  3944. public function revokeTokenCurrentUser($token = null)
  3945. {
  3946. if ($token) {
  3947. $response = [
  3948. array(
  3949. 'function' => 'query',
  3950. 'query' => array(
  3951. 'DELETE FROM tokens WHERE token = ?',
  3952. [$token]
  3953. )
  3954. ),
  3955. ];
  3956. } else {
  3957. $response = [
  3958. array(
  3959. 'function' => 'query',
  3960. 'query' => array(
  3961. 'DELETE FROM tokens WHERE user_id = ?',
  3962. [$this->user['userID']]
  3963. )
  3964. ),
  3965. ];
  3966. }
  3967. return $this->processQueries($response);
  3968. }
  3969. public function revokeToken($token = null)
  3970. {
  3971. if (!$token) {
  3972. $this->setAPIResponse('error', 'Token was not supplied', 422);
  3973. return false;
  3974. }
  3975. $response = [
  3976. array(
  3977. 'function' => 'query',
  3978. 'query' => array(
  3979. 'DELETE FROM tokens WHERE token = ?',
  3980. [$token]
  3981. )
  3982. ),
  3983. ];
  3984. $this->setAPIResponse('success', 'Token revoked', 204);
  3985. return $this->processQueries($response);
  3986. }
  3987. public function revokeTokenByIdCurrentUser($id = null)
  3988. {
  3989. if (!$id) {
  3990. $this->setAPIResponse('error', 'Id was not supplied', 422);
  3991. return false;
  3992. }
  3993. $response = [
  3994. array(
  3995. 'function' => 'query',
  3996. 'query' => array(
  3997. 'DELETE FROM tokens WHERE id = ? AND user_id = ?',
  3998. $id,
  3999. $this->user['userID']
  4000. )
  4001. ),
  4002. ];
  4003. $this->setAPIResponse('success', 'Token revoked', 204);
  4004. return $this->processQueries($response);
  4005. }
  4006. public function updateUserPassword($password, $id)
  4007. {
  4008. $response = [
  4009. array(
  4010. 'function' => 'query',
  4011. 'query' => array(
  4012. 'UPDATE users SET',
  4013. ['password' => password_hash($password, PASSWORD_BCRYPT)],
  4014. 'WHERE id = ?',
  4015. $id
  4016. )
  4017. ),
  4018. ];
  4019. return $this->processQueries($response);
  4020. }
  4021. public function updateUserPlexToken($token, $id)
  4022. {
  4023. $response = [
  4024. array(
  4025. 'function' => 'query',
  4026. 'query' => array(
  4027. 'UPDATE users SET',
  4028. ['plex_token' => $token],
  4029. 'WHERE id = ?',
  4030. $id
  4031. )
  4032. ),
  4033. ];
  4034. return $this->processQueries($response);
  4035. }
  4036. public function getUserTabsAndCategories($type = null)
  4037. {
  4038. if (!$this->hasDB()) {
  4039. return false;
  4040. }
  4041. $sort = ($this->config['unsortedTabs'] == 'top') ? 'DESC' : 'ASC';
  4042. $response = [
  4043. array(
  4044. 'function' => 'fetchAll',
  4045. 'query' => array(
  4046. 'SELECT * FROM tabs WHERE `group_id` >= ? AND `enabled` = 1 ORDER BY `order` ' . $sort,
  4047. $this->user['groupID']
  4048. ),
  4049. 'key' => 'tabs'
  4050. ),
  4051. array(
  4052. 'function' => 'fetchAll',
  4053. 'query' => array(
  4054. 'SELECT * FROM categories ORDER BY `order` ASC',
  4055. ),
  4056. 'key' => 'categories'
  4057. ),
  4058. ];
  4059. $queries = $this->processQueries($response);
  4060. $this->applyTabVariables($queries['tabs']);
  4061. $all['tabs'] = $queries['tabs'];
  4062. foreach ($queries['tabs'] as $k => $v) {
  4063. $v['access_url'] = (!empty($v['url_local']) && ($v['url_local'] !== null) && ($v['url_local'] !== 'null') && $this->isLocal() && $v['type'] !== 0) ? $v['url_local'] : $v['url'];
  4064. }
  4065. $count = array_map(function ($element) {
  4066. return $element['category_id'];
  4067. }, $queries['tabs']);
  4068. $count = (array_count_values($count));
  4069. foreach ($queries['categories'] as $k => $v) {
  4070. $v['count'] = isset($count[$v['category_id']]) ? $count[$v['category_id']] : 0;
  4071. }
  4072. $all['categories'] = $queries['categories'];
  4073. switch ($type) {
  4074. case 'categories':
  4075. return $all['categories'];
  4076. case 'tabs':
  4077. return $all['tabs'];
  4078. default:
  4079. return $all;
  4080. }
  4081. }
  4082. public function refreshList()
  4083. {
  4084. $searchTerm = "Refresh";
  4085. return array_filter($this->config, function ($k) use ($searchTerm) {
  4086. return stripos($k, $searchTerm) !== false;
  4087. }, ARRAY_FILTER_USE_KEY);
  4088. }
  4089. public function homepageOrderList()
  4090. {
  4091. $searchTerm = "homepageOrder";
  4092. $order = array_filter($this->config, function ($k) use ($searchTerm) {
  4093. return stripos($k, $searchTerm) !== false;
  4094. }, ARRAY_FILTER_USE_KEY);
  4095. asort($order);
  4096. return $order;
  4097. }
  4098. public function tautulliList()
  4099. {
  4100. $searchTerm = "tautulli_token";
  4101. return array_filter($this->config, function ($k) use ($searchTerm) {
  4102. return stripos($k, $searchTerm) !== false;
  4103. }, ARRAY_FILTER_USE_KEY);
  4104. }
  4105. public function checkPlexAdminFilled()
  4106. {
  4107. if ($this->config['plexAdmin'] == '') {
  4108. return false;
  4109. } else {
  4110. if ((strpos($this->config['plexAdmin'], '@') !== false)) {
  4111. return 'email';
  4112. } else {
  4113. return 'username';
  4114. }
  4115. }
  4116. }
  4117. public function organizrSpecialSettings()
  4118. {
  4119. // js activeInfo
  4120. return array(
  4121. 'homepage' => array(
  4122. 'refresh' => $this->refreshList(),
  4123. 'order' => $this->homepageOrderList(),
  4124. 'search' => array(
  4125. 'enabled' => $this->qualifyRequest($this->config['mediaSearchAuth']) && $this->config['mediaSearch'] == true && $this->config['plexToken'],
  4126. 'type' => $this->config['mediaSearchType'],
  4127. ),
  4128. 'requests' => [
  4129. 'service' => $this->config['defaultRequestService'],
  4130. ],
  4131. 'ombi' => array(
  4132. 'enabled' => $this->qualifyRequest($this->config['homepageOmbiAuth']) && $this->qualifyRequest($this->config['homepageOmbiRequestAuth']) && $this->config['homepageOmbiEnabled'] == true && $this->config['ssoOmbi'] && isset($_COOKIE['Auth']),
  4133. 'authView' => $this->qualifyRequest($this->config['homepageOmbiAuth']),
  4134. 'authRequest' => $this->qualifyRequest($this->config['homepageOmbiRequestAuth']),
  4135. 'sso' => (bool)$this->config['ssoOmbi'],
  4136. 'cookie' => isset($_COOKIE['Auth']),
  4137. 'alias' => (bool)$this->config['ombiAlias'],
  4138. 'ombiDefaultFilterAvailable' => (bool)$this->config['ombiDefaultFilterAvailable'],
  4139. 'ombiDefaultFilterUnavailable' => (bool)$this->config['ombiDefaultFilterUnavailable'],
  4140. 'ombiDefaultFilterApproved' => (bool)$this->config['ombiDefaultFilterApproved'],
  4141. 'ombiDefaultFilterUnapproved' => (bool)$this->config['ombiDefaultFilterUnapproved'],
  4142. 'ombiDefaultFilterDenied' => (bool)$this->config['ombiDefaultFilterDenied']
  4143. ),
  4144. 'overseerr' => array(
  4145. 'enabled' => $this->qualifyRequest($this->config['homepageOverseerrAuth']) && $this->qualifyRequest($this->config['homepageOverseerrRequestAuth']) && $this->config['homepageOverseerrEnabled'] == true && $this->config['ssoOverseerr'] && isset($_COOKIE['connect_sid']),
  4146. 'authView' => $this->qualifyRequest($this->config['homepageOverseerrAuth']),
  4147. 'authRequest' => $this->qualifyRequest($this->config['homepageOverseerrRequestAuth']),
  4148. 'sso' => (bool)$this->config['ssoOverseerr'],
  4149. 'cookie' => isset($_COOKIE['connect_sid']),
  4150. 'userSelectTv' => (bool)$this->config['homepageOverseerrRequestAuth'] == 'user',
  4151. 'overseerrDefaultFilterAvailable' => (bool)$this->config['overseerrDefaultFilterAvailable'],
  4152. 'overseerrDefaultFilterUnavailable' => (bool)$this->config['overseerrDefaultFilterUnavailable'],
  4153. 'overseerrDefaultFilterApproved' => (bool)$this->config['overseerrDefaultFilterApproved'],
  4154. 'overseerrDefaultFilterUnapproved' => (bool)$this->config['overseerrDefaultFilterUnapproved'],
  4155. 'overseerrDefaultFilterDenied' => (bool)$this->config['overseerrDefaultFilterDenied']
  4156. ),
  4157. 'jackett' => array(
  4158. 'homepageJackettBackholeDownload' => $this->config['homepageJackettBackholeDownload'] ? true : false
  4159. ),
  4160. 'options' => array(
  4161. 'alternateHomepageHeaders' => $this->config['alternateHomepageHeaders'],
  4162. 'healthChecksTags' => $this->config['healthChecksTags'],
  4163. 'titles' => array(
  4164. 'tautulli' => $this->config['tautulliHeader']
  4165. )
  4166. ),
  4167. 'media' => array(
  4168. 'jellyfin' => $this->config['homepageJellyfinInstead']
  4169. )
  4170. ),
  4171. 'sso' => array(
  4172. 'misc' => array(
  4173. 'oAuthLogin' => isset($_COOKIE['oAuth']),
  4174. 'rememberMe' => $this->config['rememberMe'],
  4175. 'rememberMeDays' => $this->config['rememberMeDays']
  4176. ),
  4177. 'plex' => array(
  4178. 'enabled' => (bool)$this->config['ssoPlex'],
  4179. 'cookie' => isset($_COOKIE['mpt']),
  4180. 'machineID' => strlen($this->config['plexID']) == 40,
  4181. 'token' => $this->config['plexToken'] !== '',
  4182. 'plexAdmin' => $this->checkPlexAdminFilled(),
  4183. 'strict' => (bool)$this->config['plexStrictFriends'],
  4184. 'oAuthEnabled' => (bool)$this->config['plexoAuth'],
  4185. 'backend' => $this->config['authBackend'] == 'plex',
  4186. ),
  4187. 'tautulli' => array(
  4188. 'enabled' => (bool)$this->config['ssoTautulli'],
  4189. 'cookie' => !empty($this->tautulliList()),
  4190. 'url' => ($this->config['tautulliURL'] !== '') ? $this->config['tautulliURL'] : false,
  4191. ),
  4192. 'overseerr' => array(
  4193. 'enabled' => (bool)$this->config['ssoOverseerr'],
  4194. 'cookie' => isset($_COOKIE['connect.sid']),
  4195. 'url' => ($this->config['overseerrURL'] !== '') ? $this->config['overseerrURL'] : false,
  4196. 'api' => $this->config['overseerrToken'] !== '',
  4197. ),
  4198. 'petio' => array(
  4199. 'enabled' => (bool)$this->config['ssoPetio'],
  4200. 'cookie' => isset($_COOKIE['petio_jwt']),
  4201. 'url' => ($this->config['petioURL'] !== '') ? $this->config['petioURL'] : false,
  4202. 'api' => $this->config['petioToken'] !== '',
  4203. ),
  4204. 'ombi' => array(
  4205. 'enabled' => (bool)$this->config['ssoOmbi'],
  4206. 'cookie' => isset($_COOKIE['Auth']),
  4207. 'url' => ($this->config['ombiURL'] !== '') ? $this->config['ombiURL'] : false,
  4208. 'api' => $this->config['ombiToken'] !== '',
  4209. ),
  4210. 'jellyfin' => array(
  4211. 'enabled' => (bool)$this->config['ssoJellyfin'],
  4212. 'url' => ($this->config['jellyfinURL'] !== '') ? $this->config['jellyfinURL'] : false,
  4213. 'ssoUrl' => ($this->config['jellyfinSSOURL'] !== '') ? $this->config['jellyfinSSOURL'] : false,
  4214. ),
  4215. ),
  4216. 'ping' => array(
  4217. 'onlineSound' => $this->config['pingOnlineSound'],
  4218. 'offlineSound' => $this->config['pingOfflineSound'],
  4219. 'statusSounds' => $this->config['statusSounds'],
  4220. 'auth' => $this->config['pingAuth'],
  4221. 'authMessage' => $this->config['pingAuthMessage'],
  4222. 'authMs' => $this->config['pingAuthMs'],
  4223. 'ms' => $this->config['pingMs'],
  4224. 'adminRefresh' => $this->config['adminPingRefresh'],
  4225. 'everyoneRefresh' => $this->config['otherPingRefresh'],
  4226. ),
  4227. 'notifications' => array(
  4228. 'backbone' => $this->config['notificationBackbone'],
  4229. 'position' => $this->config['notificationPosition']
  4230. ),
  4231. 'lockout' => array(
  4232. 'enabled' => $this->config['lockoutSystem'],
  4233. 'timer' => $this->config['lockoutTimeout'],
  4234. 'minGroup' => $this->config['lockoutMinAuth'],
  4235. 'maxGroup' => $this->config['lockoutMaxAuth']
  4236. ),
  4237. 'user' => array(
  4238. 'agent' => isset($_SERVER ['HTTP_USER_AGENT']) ? $_SERVER ['HTTP_USER_AGENT'] : null,
  4239. 'oAuthLogin' => isset($_COOKIE['oAuth']),
  4240. 'local' => $this->isLocal(),
  4241. 'ip' => $this->userIP()
  4242. ),
  4243. 'login' => array(
  4244. 'rememberMe' => $this->config['rememberMe'],
  4245. 'rememberMeDays' => $this->config['rememberMeDays'],
  4246. 'wanDomain' => $this->config['wanDomain'],
  4247. 'localAddress' => $this->config['localAddress'],
  4248. 'enableLocalAddressForward' => $this->config['enableLocalAddressForward'],
  4249. ),
  4250. 'misc' => array(
  4251. 'installedPlugins' => $this->qualifyRequest(1) ? $this->config['installedPlugins'] : '',
  4252. 'installedThemes' => $this->qualifyRequest(1) ? $this->config['installedThemes'] : '',
  4253. 'return' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false,
  4254. 'authDebug' => $this->config['authDebug'],
  4255. 'minimalLoginScreen' => $this->config['minimalLoginScreen'],
  4256. 'unsortedTabs' => $this->config['unsortedTabs'],
  4257. 'authType' => $this->config['authType'],
  4258. 'authBackend' => $this->config['authBackend'],
  4259. 'newMessageSound' => (isset($this->config['CHAT-newMessageSound-include'])) ? $this->config['CHAT-newMessageSound-include'] : '',
  4260. 'uuid' => ($this->config['uuid']) ?? null,
  4261. 'docker' => $this->qualifyRequest(1) ? $this->docker : '',
  4262. 'githubCommit' => $this->qualifyRequest(1) ? $this->commit : '',
  4263. 'schema' => $this->qualifyRequest(1) ? $this->getSchema() : '',
  4264. 'debugArea' => $this->qualifyRequest($this->config['debugAreaAuth']),
  4265. 'debugErrors' => $this->config['debugErrors'],
  4266. 'sandbox' => $this->config['sandbox'],
  4267. 'expandCategoriesByDefault' => $this->config['expandCategoriesByDefault'],
  4268. 'autoCollapseCategories' => $this->config['autoCollapseCategories'],
  4269. 'autoExpandNavBar' => $this->config['autoExpandNavBar'],
  4270. 'sideMenuCollapsed' => $this->config['allowCollapsableSideMenu'] && $this->config['sideMenuCollapsed'],
  4271. 'collapseSideMenuOnClick' => $this->config['allowCollapsableSideMenu'] && $this->config['collapseSideMenuOnClick']
  4272. ),
  4273. 'menuLink' => array(
  4274. 'githubMenuLink' => $this->config['githubMenuLink'],
  4275. 'organizrSupportMenuLink' => $this->config['organizrSupportMenuLink'],
  4276. 'organizrDocsMenuLink' => $this->config['organizrDocsMenuLink'],
  4277. 'organizrSignoutMenuLink' => $this->config['organizrSignoutMenuLink'],
  4278. 'organizrFeatureRequestLink' => $this->config['organizrFeatureRequestLink']
  4279. )
  4280. );
  4281. }
  4282. public function getLog($log, $reverse = true)
  4283. {
  4284. switch ($log) {
  4285. case 'login':
  4286. case 'loginLog':
  4287. case 'loginlog':
  4288. $file = $this->organizrLoginLog;
  4289. $parent = 'auth';
  4290. break;
  4291. case 'org':
  4292. case 'organizr':
  4293. case 'organizrLog':
  4294. case 'orglog':
  4295. $file = $this->organizrLog;
  4296. $parent = 'log_items';
  4297. break;
  4298. default:
  4299. $this->setAPIResponse('error', 'Log not defined', 404);
  4300. return null;
  4301. }
  4302. if (!file_exists($file)) {
  4303. $this->setAPIResponse('error', 'Log does not exist', 404);
  4304. return null;
  4305. }
  4306. $getLog = str_replace("\r\ndate", "date", file_get_contents($file));
  4307. $gotLog = json_decode($getLog, true);
  4308. return ($reverse) ? array_reverse($gotLog[$parent]) : $gotLog[$parent];
  4309. }
  4310. public function purgeLog($log)
  4311. {
  4312. switch ($log) {
  4313. case 'login':
  4314. case 'loginLog':
  4315. case 'loginlog':
  4316. $file = $this->organizrLoginLog;
  4317. break;
  4318. case 'org':
  4319. case 'organizr':
  4320. case 'organizrLog':
  4321. case 'orgLog':
  4322. case 'orglog':
  4323. $file = $this->organizrLog;
  4324. break;
  4325. default:
  4326. $this->setAPIResponse('error', 'Log not defined', 404);
  4327. return null;
  4328. }
  4329. if (file_exists($file)) {
  4330. if (unlink($file)) {
  4331. $this->writeLog('success', 'Log Management Function - Log: ' . $log . ' has been purged/deleted', 'SYSTEM');
  4332. $this->setAPIResponse(null, 'Log purged');
  4333. return true;
  4334. } else {
  4335. $this->writeLog('error', 'Log Management Function - Log: ' . $log . ' - Error Occurred', 'SYSTEM');
  4336. $this->setAPIResponse('error', 'Log could not be purged', 500);
  4337. return false;
  4338. }
  4339. } else {
  4340. $this->setAPIResponse('error', 'Log does not exist', 404);
  4341. return false;
  4342. }
  4343. }
  4344. public function checkLog($path)
  4345. {
  4346. if (file_exists($path)) {
  4347. if (filesize($path) > 500000) {
  4348. rename($path, $path . '[' . date('Y-m-d') . '].json');
  4349. return false;
  4350. }
  4351. return true;
  4352. } else {
  4353. return false;
  4354. }
  4355. }
  4356. public function writeLoginLog($username, $authType)
  4357. {
  4358. $username = htmlspecialchars($username, ENT_QUOTES);
  4359. if ($this->checkLog($this->organizrLoginLog)) {
  4360. $getLog = str_replace("\r\ndate", "date", file_get_contents($this->organizrLoginLog));
  4361. $gotLog = json_decode($getLog, true);
  4362. }
  4363. $logEntryFirst = array('logType' => 'login_log', 'auth' => array(array('date' => date("Y-m-d H:i:s"), 'utc_date' => $this->currentTime, 'username' => $username, 'ip' => $this->userIP(), 'auth_type' => $authType)));
  4364. $logEntry = array('date' => date("Y-m-d H:i:s"), 'utc_date' => $this->currentTime, 'username' => $username, 'ip' => $this->userIP(), 'auth_type' => $authType);
  4365. if (isset($gotLog)) {
  4366. array_push($gotLog["auth"], $logEntry);
  4367. $writeFailLog = str_replace("date", "\r\ndate", json_encode($gotLog));
  4368. } else {
  4369. $writeFailLog = str_replace("date", "\r\ndate", json_encode($logEntryFirst));
  4370. }
  4371. file_put_contents($this->organizrLoginLog, $writeFailLog);
  4372. }
  4373. public function writeLog($type = 'error', $message = null, $username = null)
  4374. {
  4375. $this->timeExecution = $this->timeExecution($this->timeExecution);
  4376. $message = $message . ' [Execution Time: ' . $this->formatSeconds($this->timeExecution) . ']';
  4377. $username = ($username) ? htmlspecialchars($username, ENT_QUOTES) : $this->user['username'] ?? 'SYSTEM';
  4378. if ($this->checkLog($this->organizrLog)) {
  4379. $getLog = str_replace("\r\ndate", "date", file_get_contents($this->organizrLog));
  4380. $gotLog = json_decode($getLog, true);
  4381. }
  4382. $logEntryFirst = array('logType' => 'organizr_log', 'log_items' => array(array('date' => date("Y-m-d H:i:s"), 'utc_date' => $this->currentTime, 'type' => $type, 'username' => $username, 'ip' => $this->userIP(), 'message' => $message)));
  4383. $logEntry = array('date' => date("Y-m-d H:i:s"), 'utc_date' => $this->currentTime, 'type' => $type, 'username' => $username, 'ip' => $this->userIP(), 'message' => $message);
  4384. if (isset($gotLog)) {
  4385. array_push($gotLog["log_items"], $logEntry);
  4386. $writeFailLog = str_replace("date", "\r\ndate", json_encode($gotLog));
  4387. } else {
  4388. $writeFailLog = str_replace("date", "\r\ndate", json_encode($logEntryFirst));
  4389. }
  4390. file_put_contents($this->organizrLog, $writeFailLog);
  4391. }
  4392. public function isApprovedRequest($method, $data)
  4393. {
  4394. $requesterToken = $this->getallheaders()['Token'] ?? ($_GET['apikey'] ?? false);
  4395. $apiKey = ($this->config['organizrAPI']) ?? null;
  4396. if (isset($data['formKey'])) {
  4397. $formKey = $data['formKey'];
  4398. } elseif (isset($this->getallheaders()['Formkey'])) {
  4399. $formKey = $this->getallheaders()['Formkey'];
  4400. } elseif (isset($this->getallheaders()['formkey'])) {
  4401. $formKey = $this->getallheaders()['formkey'];
  4402. } elseif (isset($this->getallheaders()['formKey'])) {
  4403. $formKey = $this->getallheaders()['formKey'];
  4404. } elseif (isset($this->getallheaders()['FormKey'])) {
  4405. $formKey = $this->getallheaders()['FormKey'];
  4406. } else {
  4407. $formKey = false;
  4408. }
  4409. // Check token or API key
  4410. // If API key, return 0 for admin
  4411. if (strlen($requesterToken) == 20 && $requesterToken == $apiKey) {
  4412. //DO API CHECK
  4413. return true;
  4414. } elseif ($method == 'POST') {
  4415. if ($this->checkFormKey($formKey)) {
  4416. return true;
  4417. } else {
  4418. $this->writeLog('error', 'API ERROR: Unable to authenticate Form Key: ' . $formKey, $this->user['username']);
  4419. return false;
  4420. }
  4421. } else {
  4422. return true;
  4423. }
  4424. return false;
  4425. }
  4426. public function checkFormKey($formKey = '')
  4427. {
  4428. return password_verify(substr($this->config['organizrHash'], 2, 10), $formKey);
  4429. }
  4430. public function buildHomepage()
  4431. {
  4432. $homepageOrder = $this->homepageOrderList();
  4433. $homepageBuilt = '';
  4434. foreach ($homepageOrder as $key => $value) {
  4435. //new way
  4436. if (method_exists($this, $key)) {
  4437. $homepageBuilt .= $this->$key();
  4438. } elseif (strpos($key, 'homepageOrdercustomhtml') !== false) {
  4439. $iteration = substr($key, -2);
  4440. $homepageBuilt .= $this->homepageOrdercustomhtml($iteration);
  4441. } else {
  4442. $homepageBuilt .= '<div id="' . $key . '"></div>';
  4443. }
  4444. //old way
  4445. //$homepageBuilt .= $this->buildHomepageItem($key);
  4446. }
  4447. return $homepageBuilt;
  4448. }
  4449. public function buildHomepageSettings()
  4450. {
  4451. $homepageOrder = $this->homepageOrderList();
  4452. $homepageList = '<div class="col-lg-12"><h4 lang="en">Drag Homepage Items to Order Them</h4></div><div id="homepage-items-sort" class="external-events">';
  4453. $inputList = '<form id="homepage-values" class="row">';
  4454. foreach ($homepageOrder as $key => $val) {
  4455. switch ($key) {
  4456. case 'homepageOrdercustomhtml01':
  4457. case 'homepageOrdercustomhtml02':
  4458. case 'homepageOrdercustomhtml03':
  4459. case 'homepageOrdercustomhtml04':
  4460. case 'homepageOrdercustomhtml05':
  4461. case 'homepageOrdercustomhtml06':
  4462. case 'homepageOrdercustomhtml07':
  4463. case 'homepageOrdercustomhtml08':
  4464. $iteration = substr($key, -2);
  4465. $class = 'bg-info';
  4466. $image = 'plugins/images/tabs/HTML5.png';
  4467. if (!$this->config['homepageCustomHTML' . $iteration . 'Enabled']) {
  4468. $class .= ' faded';
  4469. }
  4470. break;
  4471. case 'homepageOrdertransmission':
  4472. $class = 'bg-transmission';
  4473. $image = 'plugins/images/tabs/transmission.png';
  4474. if (!$this->config['homepageTransmissionEnabled']) {
  4475. $class .= ' faded';
  4476. }
  4477. break;
  4478. case 'homepageOrdernzbget':
  4479. $class = 'bg-nzbget';
  4480. $image = 'plugins/images/tabs/nzbget.png';
  4481. if (!$this->config['homepageNzbgetEnabled']) {
  4482. $class .= ' faded';
  4483. }
  4484. break;
  4485. case 'homepageOrderjdownloader':
  4486. $class = 'bg-sab';
  4487. $image = 'plugins/images/tabs/jdownloader.png';
  4488. if (!$this->config['homepageJdownloaderEnabled']) {
  4489. $class .= ' faded';
  4490. }
  4491. break;
  4492. case 'homepageOrdersabnzbd':
  4493. $class = 'bg-sab';
  4494. $image = 'plugins/images/tabs/sabnzbd.png';
  4495. if (!$this->config['homepageSabnzbdEnabled']) {
  4496. $class .= ' faded';
  4497. }
  4498. break;
  4499. case 'homepageOrderdeluge':
  4500. $class = 'bg-deluge';
  4501. $image = 'plugins/images/tabs/deluge.png';
  4502. if (!$this->config['homepageDelugeEnabled']) {
  4503. $class .= ' faded';
  4504. }
  4505. break;
  4506. case 'homepageOrderqBittorrent':
  4507. $class = 'bg-qbit';
  4508. $image = 'plugins/images/tabs/qBittorrent.png';
  4509. if (!$this->config['homepageqBittorrentEnabled']) {
  4510. $class .= ' faded';
  4511. }
  4512. break;
  4513. case 'homepageOrderuTorrent':
  4514. $class = 'bg-qbit';
  4515. $image = 'plugins/images/tabs/utorrent.png';
  4516. if (!$this->config['homepageuTorrentEnabled']) {
  4517. $class .= ' faded';
  4518. }
  4519. break;
  4520. case 'homepageOrderrTorrent':
  4521. $class = 'bg-qbit';
  4522. $image = 'plugins/images/tabs/rTorrent.png';
  4523. if (!$this->config['homepagerTorrentEnabled']) {
  4524. $class .= ' faded';
  4525. }
  4526. break;
  4527. case 'homepageOrderplexnowplaying':
  4528. case 'homepageOrderplexrecent':
  4529. case 'homepageOrderplexplaylist':
  4530. $class = 'bg-plex';
  4531. $image = 'plugins/images/tabs/plex.png';
  4532. if (!$this->config['homepagePlexEnabled']) {
  4533. $class .= ' faded';
  4534. }
  4535. break;
  4536. case 'homepageOrderembynowplaying':
  4537. case 'homepageOrderembyrecent':
  4538. $class = 'bg-emby';
  4539. $image = 'plugins/images/tabs/emby.png';
  4540. if (!$this->config['homepageEmbyEnabled']) {
  4541. $class .= ' faded';
  4542. }
  4543. break;
  4544. case 'homepageOrderjellyfinnowplaying':
  4545. case 'homepageOrderjellyfinrecent':
  4546. $class = 'bg-jellyfin';
  4547. $image = 'plugins/images/tabs/jellyfin.png';
  4548. if (!$this->config['homepageJellyfinEnabled']) {
  4549. $class .= ' faded';
  4550. }
  4551. break;
  4552. case 'homepageOrderombi':
  4553. $class = 'bg-inverse';
  4554. $image = 'plugins/images/tabs/ombi.png';
  4555. if (!$this->config['homepageOmbiEnabled']) {
  4556. $class .= ' faded';
  4557. }
  4558. break;
  4559. case 'homepageOrderoverseerr':
  4560. $class = 'bg-inverse';
  4561. $image = 'plugins/images/tabs/overseerr.png';
  4562. if (!$this->config['homepageOverseerrEnabled']) {
  4563. $class .= ' faded';
  4564. }
  4565. break;
  4566. case 'homepageOrdercalendar':
  4567. $class = 'bg-primary';
  4568. $image = 'plugins/images/tabs/calendar.png';
  4569. if (!$this->config['homepageCalendarEnabled'] && !$this->config['homepageSonarrEnabled'] && !$this->config['homepageRadarrEnabled'] && !$this->config['homepageSickrageEnabled'] && !$this->config['homepageCouchpotatoEnabled']) {
  4570. $class .= ' faded';
  4571. }
  4572. break;
  4573. case 'homepageOrderdownloader':
  4574. $class = 'bg-inverse';
  4575. $image = 'plugins/images/tabs/downloader.png';
  4576. if (!$this->config['jdownloaderCombine'] && !$this->config['sabnzbdCombine'] && !$this->config['nzbgetCombine'] && !$this->config['rTorrentCombine'] && !$this->config['delugeCombine'] && !$this->config['transmissionCombine'] && !$this->config['qBittorrentCombine'] && !$this->config['uTorrentCombine']) {
  4577. $class .= ' faded';
  4578. }
  4579. break;
  4580. case 'homepageOrderhealthchecks':
  4581. $class = 'bg-healthchecks';
  4582. $image = 'plugins/images/tabs/healthchecks.png';
  4583. if (!$this->config['homepageHealthChecksEnabled']) {
  4584. $class .= ' faded';
  4585. }
  4586. break;
  4587. case 'homepageOrderunifi':
  4588. $class = 'bg-info';
  4589. $image = 'plugins/images/tabs/ubnt.png';
  4590. if (!$this->config['homepageUnifiEnabled']) {
  4591. $class .= ' faded';
  4592. }
  4593. break;
  4594. case 'homepageOrdertautulli':
  4595. $class = 'bg-info';
  4596. $image = 'plugins/images/tabs/tautulli.png';
  4597. if (!$this->config['homepageTautulliEnabled']) {
  4598. $class .= ' faded';
  4599. }
  4600. break;
  4601. case 'homepageOrderPihole':
  4602. $class = 'bg-info';
  4603. $image = 'plugins/images/tabs/pihole.png';
  4604. if (!$this->config['homepagePiholeEnabled']) {
  4605. $class .= ' faded';
  4606. }
  4607. break;
  4608. case 'homepageOrderMonitorr':
  4609. $class = 'bg-info';
  4610. $image = 'plugins/images/tabs/monitorr.png';
  4611. if (!$this->config['homepageMonitorrEnabled']) {
  4612. $class .= ' faded';
  4613. }
  4614. break;
  4615. case 'homepageOrderWeatherAndAir':
  4616. $class = 'bg-success';
  4617. $image = 'plugins/images/tabs/wind.png';
  4618. if (!$this->config['homepageWeatherAndAirEnabled']) {
  4619. $class .= ' faded';
  4620. }
  4621. break;
  4622. case 'homepageOrderSpeedtest':
  4623. $class = 'bg-success';
  4624. $image = 'plugins/images/tabs/speedtest-icon.png';
  4625. if (!$this->config['homepageSpeedtestEnabled']) {
  4626. $class .= ' faded';
  4627. }
  4628. break;
  4629. case 'homepageOrderNetdata':
  4630. $class = 'bg-success';
  4631. $image = 'plugins/images/tabs/netdata.png';
  4632. if (!$this->config['homepageNetdataEnabled']) {
  4633. $class .= ' faded';
  4634. }
  4635. break;
  4636. case 'homepageOrderOctoprint':
  4637. $class = 'bg-success';
  4638. $image = 'plugins/images/tabs/octoprint.png';
  4639. if (!$this->config['homepageOctoprintEnabled']) {
  4640. $class .= ' faded';
  4641. }
  4642. break;
  4643. case 'homepageOrderSonarrQueue':
  4644. $class = 'bg-sonarr';
  4645. $image = 'plugins/images/tabs/sonarr.png';
  4646. if (!$this->config['homepageSonarrQueueEnabled']) {
  4647. $class .= ' faded';
  4648. }
  4649. break;
  4650. case 'homepageOrderRadarrQueue':
  4651. $class = 'bg-radarr';
  4652. $image = 'plugins/images/tabs/radarr.png';
  4653. if (!$this->config['homepageRadarrQueueEnabled']) {
  4654. $class .= ' faded';
  4655. }
  4656. break;
  4657. case 'homepageOrderJackett':
  4658. $class = 'bg-inverse';
  4659. $image = 'plugins/images/tabs/jackett.png';
  4660. if (!$this->config['homepageJackettEnabled']) {
  4661. $class .= ' faded';
  4662. }
  4663. break;
  4664. default:
  4665. $class = 'blue-bg';
  4666. $image = '';
  4667. break;
  4668. }
  4669. $homepageList .= '
  4670. <div class="col-md-3 col-xs-12 sort-homepage m-t-10 hvr-grow clearfix">
  4671. <div class="homepage-drag fc-event ' . $class . ' lazyload" data-src="' . $image . '">
  4672. <span class="ordinal-position text-uppercase badge bg-org homepage-number" data-link="' . $key . '" style="float:left;width: 30px;">' . $val . '</span>
  4673. <span class="homepage-text">&nbsp; ' . strtoupper(substr($key, 13)) . '</span>
  4674. </div>
  4675. </div>
  4676. ';
  4677. $inputList .= '<input type="hidden" name="' . $key . '">';
  4678. }
  4679. $homepageList .= '</div>';
  4680. $inputList .= '</form>';
  4681. return $homepageList . $inputList;
  4682. }
  4683. public function setGroupOptionsVariable()
  4684. {
  4685. $this->groupOptions = $this->groupSelect();
  4686. }
  4687. public function getSettingsHomepageItem($item)
  4688. {
  4689. $items = $this->getSettingsHomepage();
  4690. foreach ($items as $k => $v) {
  4691. if (strtolower($v['name']) === strtolower($item)) {
  4692. $functionName = $v['settingsArray'];
  4693. return $this->$functionName();
  4694. }
  4695. }
  4696. $this->setAPIResponse('error', 'Homepage item was not found', 404);
  4697. return null;
  4698. }
  4699. public function getSettingsHomepageItemDebug($service)
  4700. {
  4701. $service = $this->getSettingsHomepageItem($service);
  4702. if ($service) {
  4703. $debug = [];
  4704. foreach ($service['settings'] as $category => $items) {
  4705. if ($category !== 'About' && $category !== 'Test Connection') {
  4706. foreach ($items as $item) {
  4707. if ($item['type'] !== 'html' && $item['type'] !== 'blank' && $item['type'] !== 'button') {
  4708. if ((stripos($item['name'], 'token') !== false) || (stripos($item['name'], 'key') !== false) || (stripos($item['name'], 'password'))) {
  4709. if ($item['value'] !== '') {
  4710. $item['value'] = '***redacted***';
  4711. }
  4712. }
  4713. $debug[$category][$item['name']] = $item['value'];
  4714. }
  4715. }
  4716. }
  4717. }
  4718. return $debug;
  4719. }
  4720. $this->setAPIResponse('error', 'Homepage item was not found', 404);
  4721. return null;
  4722. }
  4723. public function getSettingsHomepage()
  4724. {
  4725. $this->setGroupOptionsVariable();
  4726. return $this->getHomepageSettingsCombined();
  4727. }
  4728. public function isTabNameTaken($name, $id = null)
  4729. {
  4730. if ($id) {
  4731. $response = [
  4732. array(
  4733. 'function' => 'fetchAll',
  4734. 'query' => array(
  4735. 'SELECT * FROM tabs WHERE `name` LIKE ? AND `id` != ?',
  4736. $name,
  4737. $id
  4738. )
  4739. ),
  4740. ];
  4741. } else {
  4742. $response = [
  4743. array(
  4744. 'function' => 'fetchAll',
  4745. 'query' => array(
  4746. 'SELECT * FROM tabs WHERE `name` LIKE ?',
  4747. $name
  4748. )
  4749. ),
  4750. ];
  4751. }
  4752. return $this->processQueries($response);
  4753. }
  4754. public function isCategoryNameTaken($name, $id = null)
  4755. {
  4756. if ($id) {
  4757. $response = [
  4758. array(
  4759. 'function' => 'fetchAll',
  4760. 'query' => array(
  4761. 'SELECT * FROM categories WHERE `category` LIKE ? AND `id` != ?',
  4762. $name,
  4763. $id
  4764. )
  4765. ),
  4766. ];
  4767. } else {
  4768. $response = [
  4769. array(
  4770. 'function' => 'fetchAll',
  4771. 'query' => array(
  4772. 'SELECT * FROM categories WHERE `category` LIKE ?',
  4773. $name
  4774. )
  4775. ),
  4776. ];
  4777. }
  4778. return $this->processQueries($response);
  4779. }
  4780. public function isGroupNameTaken($name, $id = null)
  4781. {
  4782. if ($id) {
  4783. $response = [
  4784. array(
  4785. 'function' => 'fetchAll',
  4786. 'query' => array(
  4787. 'SELECT * FROM groups WHERE `group` LIKE ? AND `id` != ?',
  4788. $name,
  4789. $id
  4790. )
  4791. ),
  4792. ];
  4793. } else {
  4794. $response = [
  4795. array(
  4796. 'function' => 'fetchAll',
  4797. 'query' => array(
  4798. 'SELECT * FROM groups WHERE `group` LIKE ?',
  4799. $name
  4800. )
  4801. ),
  4802. ];
  4803. }
  4804. return $this->processQueries($response);
  4805. }
  4806. public function getTableColumns($table)
  4807. {
  4808. $response = [
  4809. array(
  4810. 'function' => 'fetchAll',
  4811. 'query' => array(
  4812. 'PRAGMA table_info(?)',
  4813. $table
  4814. )
  4815. ),
  4816. ];
  4817. return $this->processQueries($response);
  4818. }
  4819. public function getTableColumnsFormatted($table)
  4820. {
  4821. $columns = $this->getTableColumns($table);
  4822. if ($columns) {
  4823. $columnsFormatted = [];
  4824. foreach ($columns as $k => $v) {
  4825. $columnsFormatted[$v['name']] = $v;
  4826. }
  4827. return $columnsFormatted;
  4828. } else {
  4829. return false;
  4830. }
  4831. }
  4832. public function getTabById($id)
  4833. {
  4834. $response = [
  4835. array(
  4836. 'function' => 'fetch',
  4837. 'query' => array(
  4838. 'SELECT * FROM tabs WHERE `id` = ?',
  4839. $id
  4840. )
  4841. ),
  4842. ];
  4843. return $this->processQueries($response);
  4844. }
  4845. public function getTabGroupByTabName($tab)
  4846. {
  4847. $response = [
  4848. array(
  4849. 'function' => 'fetch',
  4850. 'query' => array(
  4851. 'SELECT group_id FROM tabs WHERE name LIKE %~like~',
  4852. $tab
  4853. )
  4854. ),
  4855. ];
  4856. $query = $this->processQueries($response);
  4857. return $query ? $query['group_id'] : 0;
  4858. }
  4859. public function getCategoryById($id)
  4860. {
  4861. $response = [
  4862. array(
  4863. 'function' => 'fetch',
  4864. 'query' => array(
  4865. 'SELECT * FROM categories WHERE `id` = ?',
  4866. $id
  4867. )
  4868. ),
  4869. ];
  4870. return $this->processQueries($response);
  4871. }
  4872. public function getGroupUserCountById($id)
  4873. {
  4874. $response = [
  4875. array(
  4876. 'function' => 'fetchSingle',
  4877. 'query' => array(
  4878. 'SELECT count(username) AS count FROM groups INNER JOIN users ON users.group_id = groups.group_id AND groups.id = ?',
  4879. $id
  4880. )
  4881. ),
  4882. ];
  4883. return $this->processQueries($response);
  4884. }
  4885. public function getGroupById($id)
  4886. {
  4887. $response = [
  4888. array(
  4889. 'function' => 'fetch',
  4890. 'query' => array(
  4891. 'SELECT * FROM groups WHERE `id` = ?',
  4892. $id
  4893. )
  4894. ),
  4895. ];
  4896. return $this->processQueries($response);
  4897. }
  4898. public function getGroupByGroupId($id)
  4899. {
  4900. $response = [
  4901. array(
  4902. 'function' => 'fetch',
  4903. 'query' => array(
  4904. 'SELECT * FROM groups WHERE `group_id` = ?',
  4905. $id
  4906. )
  4907. ),
  4908. ];
  4909. return $this->processQueries($response);
  4910. }
  4911. public function getDefaultGroup()
  4912. {
  4913. $response = [
  4914. array(
  4915. 'function' => 'fetch',
  4916. 'query' => array(
  4917. 'SELECT * FROM groups WHERE `default` = 1'
  4918. )
  4919. ),
  4920. ];
  4921. return $this->processQueries($response);
  4922. }
  4923. public function getDefaultGroupId()
  4924. {
  4925. $response = [
  4926. array(
  4927. 'function' => 'fetchSingle',
  4928. 'query' => array(
  4929. 'SELECT `group_id` FROM groups WHERE `default` = 1'
  4930. )
  4931. ),
  4932. ];
  4933. return $this->processQueries($response);
  4934. }
  4935. public function getDefaultCategory()
  4936. {
  4937. $response = [
  4938. array(
  4939. 'function' => 'fetch',
  4940. 'query' => array(
  4941. 'SELECT * FROM categories WHERE `default` = 1'
  4942. )
  4943. ),
  4944. ];
  4945. return $this->processQueries($response);
  4946. }
  4947. public function getDefaultCategoryId()
  4948. {
  4949. $response = [
  4950. array(
  4951. 'function' => 'fetchSingle',
  4952. 'query' => array(
  4953. 'SELECT `category_id` FROM categories WHERE `default` = 1'
  4954. )
  4955. ),
  4956. ];
  4957. return $this->processQueries($response);
  4958. }
  4959. public function getNextTabOrder()
  4960. {
  4961. $response = [
  4962. array(
  4963. 'function' => 'fetchSingle',
  4964. 'query' => array(
  4965. 'SELECT `order` from tabs ORDER BY `order` DESC'
  4966. )
  4967. ),
  4968. ];
  4969. return $this->processQueries($response);
  4970. }
  4971. public function getNextCategoryOrder()
  4972. {
  4973. $response = [
  4974. array(
  4975. 'function' => 'fetchSingle',
  4976. 'query' => array(
  4977. 'SELECT `order` from categories ORDER BY `order` DESC'
  4978. )
  4979. ),
  4980. ];
  4981. return $this->processQueries($response);
  4982. }
  4983. public function getNextGroupOrder()
  4984. {
  4985. $response = [
  4986. array(
  4987. 'function' => 'fetchSingle',
  4988. 'query' => array(
  4989. 'SELECT `group_id` from groups WHERE `group_id` != "999" ORDER BY `group_id` DESC'
  4990. )
  4991. ),
  4992. ];
  4993. return $this->processQueries($response);
  4994. }
  4995. public function getNextCategoryId()
  4996. {
  4997. $response = [
  4998. array(
  4999. 'function' => 'fetchSingle',
  5000. 'query' => array(
  5001. 'SELECT `category_id` from categories ORDER BY `category_id` DESC'
  5002. )
  5003. ),
  5004. ];
  5005. return $this->processQueries($response);
  5006. }
  5007. public function clearTabDefault()
  5008. {
  5009. $response = [
  5010. array(
  5011. 'function' => 'query',
  5012. 'query' => array(
  5013. 'UPDATE tabs SET `default` = 0'
  5014. )
  5015. ),
  5016. ];
  5017. return $this->processQueries($response);
  5018. }
  5019. public function clearCategoryDefault()
  5020. {
  5021. $response = [
  5022. array(
  5023. 'function' => 'query',
  5024. 'query' => array(
  5025. 'UPDATE categories SET `default` = 0'
  5026. )
  5027. ),
  5028. ];
  5029. return $this->processQueries($response);
  5030. }
  5031. public function clearGroupDefault()
  5032. {
  5033. $response = [
  5034. array(
  5035. 'function' => 'query',
  5036. 'query' => array(
  5037. 'UPDATE groups SET `default` = 0'
  5038. )
  5039. ),
  5040. ];
  5041. return $this->processQueries($response);
  5042. }
  5043. public function checkKeys($tabInfo, $newData)
  5044. {
  5045. foreach ($newData as $k => $v) {
  5046. if (!array_key_exists($k, $tabInfo)) {
  5047. unset($newData[$k]);
  5048. }
  5049. }
  5050. return $newData;
  5051. }
  5052. public function getTabByIdCheckUser($id)
  5053. {
  5054. $tabInfo = $this->getTabById($id);
  5055. if ($tabInfo) {
  5056. if ($this->qualifyRequest($tabInfo['group_id'], true)) {
  5057. return $tabInfo;
  5058. }
  5059. } else {
  5060. $this->setAPIResponse('error', 'id not found', 404);
  5061. return false;
  5062. }
  5063. }
  5064. public function deleteTab($id)
  5065. {
  5066. $response = [
  5067. array(
  5068. 'function' => 'query',
  5069. 'query' => array(
  5070. 'DELETE FROM tabs WHERE id = ?',
  5071. $id
  5072. )
  5073. ),
  5074. ];
  5075. $tabInfo = $this->getTabById($id);
  5076. if ($tabInfo) {
  5077. $this->writeLog('success', 'Tab Delete Function - Deleted Tab [' . $tabInfo['name'] . ']', $this->user['username']);
  5078. $this->setAPIResponse('success', 'Tab deleted', 204);
  5079. return $this->processQueries($response);
  5080. } else {
  5081. $this->setAPIResponse('error', 'id not found', 404);
  5082. return false;
  5083. }
  5084. }
  5085. public function addTab($array)
  5086. {
  5087. if (!$array) {
  5088. $this->setAPIResponse('error', 'no data was sent', 422);
  5089. return null;
  5090. }
  5091. $array = $this->checkKeys($this->getTableColumnsFormatted('tabs'), $array);
  5092. $array['group_id'] = ($array['group_id']) ?? $this->getDefaultGroupId();
  5093. $array['category_id'] = ($array['category_id']) ?? $this->getDefaultCategoryId();
  5094. $array['enabled'] = ($array['enabled']) ?? 0;
  5095. $array['default'] = ($array['default']) ?? 0;
  5096. $array['type'] = ($array['type']) ?? 1;
  5097. $array['order'] = ($array['order']) ?? $this->getNextTabOrder() + 1;
  5098. if (array_key_exists('name', $array)) {
  5099. if ($this->isTabNameTaken($array['name'])) {
  5100. $this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
  5101. return false;
  5102. }
  5103. } else {
  5104. $this->setAPIResponse('error', 'Tab name was not supplied', 422);
  5105. return false;
  5106. }
  5107. if (!array_key_exists('url', $array) && !array_key_exists('url_local', $array)) {
  5108. $this->setAPIResponse('error', 'Tab url or url_local was not supplied', 422);
  5109. return false;
  5110. }
  5111. if (!array_key_exists('image', $array)) {
  5112. $this->setAPIResponse('error', 'Tab image was not supplied', 422);
  5113. return false;
  5114. }
  5115. $response = [
  5116. array(
  5117. 'function' => 'query',
  5118. 'query' => array(
  5119. 'INSERT INTO [tabs]',
  5120. $array
  5121. )
  5122. ),
  5123. ];
  5124. $this->setAPIResponse(null, 'Tab added');
  5125. $this->writeLog('success', 'Tab Editor Function - Added Tab for [' . $array['name'] . ']', $this->user['username']);
  5126. return $this->processQueries($response);
  5127. }
  5128. public function updateTab($id, $array)
  5129. {
  5130. if (!$id || $id == '') {
  5131. $this->setAPIResponse('error', 'id was not set', 422);
  5132. return null;
  5133. }
  5134. if (!$array) {
  5135. $this->setAPIResponse('error', 'no data was sent', 422);
  5136. return null;
  5137. }
  5138. $tabInfo = $this->getTabById($id);
  5139. if ($tabInfo) {
  5140. $array = $this->checkKeys($tabInfo, $array);
  5141. } else {
  5142. $this->setAPIResponse('error', 'No tab info found', 404);
  5143. return false;
  5144. }
  5145. if (array_key_exists('name', $array)) {
  5146. if ($this->isTabNameTaken($array['name'], $id)) {
  5147. $this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
  5148. return false;
  5149. }
  5150. }
  5151. if (array_key_exists('default', $array)) {
  5152. if ($array['default']) {
  5153. $this->clearTabDefault();
  5154. }
  5155. }
  5156. $response = [
  5157. array(
  5158. 'function' => 'query',
  5159. 'query' => array(
  5160. 'UPDATE tabs SET',
  5161. $array,
  5162. 'WHERE id = ?',
  5163. $id
  5164. )
  5165. ),
  5166. ];
  5167. $this->setAPIResponse(null, 'Tab info updated');
  5168. $this->writeLog('success', 'Tab Editor Function - Edited Tab Info for [' . $tabInfo['name'] . ']', $this->user['username']);
  5169. return $this->processQueries($response);
  5170. }
  5171. public function updateTabOrder($array)
  5172. {
  5173. if (count($array) >= 1) {
  5174. foreach ($array as $tab) {
  5175. if (count($tab) !== 2) {
  5176. $this->setAPIResponse('error', 'data is malformed', 422);
  5177. break;
  5178. }
  5179. $id = $tab['id'] ?? null;
  5180. $order = $tab['order'] ?? null;
  5181. if ($id && $order) {
  5182. $response = [
  5183. array(
  5184. 'function' => 'query',
  5185. 'query' => array(
  5186. 'UPDATE tabs set `order` = ? WHERE `id` = ?',
  5187. $order,
  5188. $id
  5189. )
  5190. ),
  5191. ];
  5192. $this->processQueries($response);
  5193. $this->setAPIResponse(null, 'Tab Order updated');
  5194. } else {
  5195. $this->setAPIResponse('error', 'data is malformed', 422);
  5196. }
  5197. }
  5198. } else {
  5199. $this->setAPIResponse('error', 'data is empty or not in array', 422);
  5200. return false;
  5201. }
  5202. }
  5203. public function addCategory($array)
  5204. {
  5205. if (!$array) {
  5206. $this->setAPIResponse('error', 'no data was sent', 422);
  5207. return null;
  5208. }
  5209. $array = $this->checkKeys($this->getTableColumnsFormatted('categories'), $array);
  5210. $array['default'] = ($array['default']) ?? 0;
  5211. $array['order'] = ($array['order']) ?? $this->getNextCategoryOrder() + 1;
  5212. $array['category_id'] = ($array['category_id']) ?? $this->getNextCategoryId() + 1;
  5213. if (array_key_exists('category', $array)) {
  5214. if ($this->isCategoryNameTaken($array['category'])) {
  5215. $this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
  5216. return false;
  5217. }
  5218. } else {
  5219. $this->setAPIResponse('error', 'Category name was not supplied', 422);
  5220. return false;
  5221. }
  5222. if (!array_key_exists('image', $array)) {
  5223. $this->setAPIResponse('error', 'Category image was not supplied', 422);
  5224. }
  5225. $response = [
  5226. array(
  5227. 'function' => 'query',
  5228. 'query' => array(
  5229. 'INSERT INTO [categories]',
  5230. $array
  5231. )
  5232. ),
  5233. ];
  5234. $this->setAPIResponse(null, 'Category added');
  5235. $this->writeLog('success', 'Category Editor Function - Added Category for [' . $array['category'] . ']', $this->user['username']);
  5236. return $this->processQueries($response);
  5237. }
  5238. public function updateCategory($id, $array)
  5239. {
  5240. if (!$id || $id == '') {
  5241. $this->setAPIResponse('error', 'id was not set', 422);
  5242. return null;
  5243. }
  5244. if (!$array) {
  5245. $this->setAPIResponse('error', 'no data was sent', 422);
  5246. return null;
  5247. }
  5248. $categoryInfo = $this->getCategoryById($id);
  5249. if ($categoryInfo) {
  5250. $array = $this->checkKeys($categoryInfo, $array);
  5251. } else {
  5252. $this->setAPIResponse('error', 'No category info found', 404);
  5253. return false;
  5254. }
  5255. if (array_key_exists('category', $array)) {
  5256. if ($this->isCategoryNameTaken($array['category'], $id)) {
  5257. $this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
  5258. return false;
  5259. }
  5260. }
  5261. if (array_key_exists('default', $array)) {
  5262. if ($array['default']) {
  5263. $this->clearCategoryDefault();
  5264. }
  5265. }
  5266. $response = [
  5267. array(
  5268. 'function' => 'query',
  5269. 'query' => array(
  5270. 'UPDATE categories SET',
  5271. $array,
  5272. 'WHERE id = ?',
  5273. $id
  5274. )
  5275. ),
  5276. ];
  5277. $this->setAPIResponse(null, 'Category info updated');
  5278. $this->writeLog('success', 'Category Editor Function - Edited Category Info for [' . $categoryInfo['category'] . ']', $this->user['username']);
  5279. return $this->processQueries($response);
  5280. }
  5281. public function updateCategoryOrder($array)
  5282. {
  5283. if (count($array) >= 1) {
  5284. foreach ($array as $category) {
  5285. if (count($category) !== 2) {
  5286. $this->setAPIResponse('error', 'data is malformed', 422);
  5287. break;
  5288. }
  5289. $id = $category['id'] ?? null;
  5290. $order = $category['order'] ?? null;
  5291. if ($id && $order) {
  5292. $response = [
  5293. array(
  5294. 'function' => 'query',
  5295. 'query' => array(
  5296. 'UPDATE categories set `order` = ? WHERE `id` = ?',
  5297. $order,
  5298. $id
  5299. )
  5300. ),
  5301. ];
  5302. $this->processQueries($response);
  5303. $this->setAPIResponse(null, 'Category Order updated');
  5304. } else {
  5305. $this->setAPIResponse('error', 'data is malformed', 422);
  5306. }
  5307. }
  5308. } else {
  5309. $this->setAPIResponse('error', 'data is empty or not in array', 422);
  5310. return false;
  5311. }
  5312. }
  5313. public function deleteCategory($id)
  5314. {
  5315. $response = [
  5316. array(
  5317. 'function' => 'query',
  5318. 'query' => array(
  5319. 'DELETE FROM categories WHERE id = ?',
  5320. $id
  5321. )
  5322. ),
  5323. ];
  5324. $categoryInfo = $this->getCategoryById($id);
  5325. if ($categoryInfo) {
  5326. $this->writeLog('success', 'Category Delete Function - Deleted Category [' . $categoryInfo['category'] . ']', $this->user['username']);
  5327. $this->setAPIResponse('success', 'Category deleted', 204);
  5328. return $this->processQueries($response);
  5329. } else {
  5330. $this->setAPIResponse('error', 'id not found', 404);
  5331. return false;
  5332. }
  5333. }
  5334. public function inconspicuous(): string
  5335. {
  5336. if ($this->hasDB()) {
  5337. if ($this->config['easterEggs']) {
  5338. return '
  5339. <div class="org-rox-trigger">
  5340. <div class="org-rox">
  5341. <div class="hair"></div>
  5342. <div class="head">
  5343. <div class="ear left"></div>
  5344. <div class="ear right"></div>
  5345. <div class="face">
  5346. <div class="eye left"></div>
  5347. <div class="eye right"></div>
  5348. <div class="nose"></div>
  5349. <div class="mouth"></div>
  5350. </div>
  5351. </div>
  5352. </div>
  5353. </div>';
  5354. }
  5355. }
  5356. return '';
  5357. }
  5358. public function marketplaceFileListFormat($files, $folder, $type)
  5359. {
  5360. foreach ($files as $k => $v) {
  5361. $splitFiles = explode('|', $v);
  5362. $prePath = (strlen($k) !== 1) ? $k . '/' : $k;
  5363. foreach ($splitFiles as $file) {
  5364. $filesList[] = array(
  5365. 'fileName' => $file,
  5366. 'path' => $prePath,
  5367. 'githubPath' => 'https://raw.githubusercontent.com/causefx/Organizr/v2-' . $type . '/' . $folder . $prePath . $file
  5368. );
  5369. }
  5370. }
  5371. return $filesList;
  5372. }
  5373. public function removeTheme($theme)
  5374. {
  5375. $theme = $this->reverseCleanClassName($theme);
  5376. $array = $this->getThemesGithub();
  5377. $arrayLower = array_change_key_case($array);
  5378. if (!$array) {
  5379. $this->setAPIResponse('error', 'Could not access theme marketplace', 409);
  5380. return false;
  5381. }
  5382. if (!$arrayLower[$theme]) {
  5383. $this->setAPIResponse('error', 'Theme does not exist in marketplace', 404);
  5384. return false;
  5385. } else {
  5386. $key = array_search($theme, array_keys($arrayLower));
  5387. $theme = array_keys($array)[$key];
  5388. }
  5389. $array = $array[$theme];
  5390. $downloadList = $this->marketplaceFileListFormat($array['files'], $array['github_folder'], 'themes');
  5391. if (!$downloadList) {
  5392. $this->setAPIResponse('error', 'Could not get download list for theme', 409);
  5393. return false;
  5394. }
  5395. $name = $theme;
  5396. $version = $array['version'];
  5397. $installedThemesNew = '';
  5398. foreach ($downloadList as $k => $v) {
  5399. $file = array(
  5400. 'from' => $v['githubPath'],
  5401. 'to' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'] . $v['fileName']),
  5402. 'path' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'])
  5403. );
  5404. if (!$this->rrmdir($file['to'])) {
  5405. $this->writeLog('error', 'Theme Function - Remove File Failed for: ' . $v['githubPath'], $this->user['username']);
  5406. return false;
  5407. }
  5408. }
  5409. if ($this->config['installedThemes'] !== '') {
  5410. $installedThemes = explode('|', $this->config['installedThemes']);
  5411. foreach ($installedThemes as $k => $v) {
  5412. $themes = explode(':', $v);
  5413. $installedThemesList[$themes[0]] = $themes[1];
  5414. }
  5415. if (isset($installedThemesList[$name])) {
  5416. foreach ($installedThemesList as $k => $v) {
  5417. if ($k !== $name) {
  5418. if ($installedThemesNew == '') {
  5419. $installedThemesNew .= $k . ':' . $v;
  5420. } else {
  5421. $installedThemesNew .= '|' . $k . ':' . $v;
  5422. }
  5423. }
  5424. }
  5425. }
  5426. }
  5427. $this->updateConfig(array('installedThemes' => $installedThemesNew));
  5428. $this->setAPIResponse('success', 'Theme removed', 200, $installedThemesNew);
  5429. return true;
  5430. }
  5431. public function installTheme($theme)
  5432. {
  5433. $theme = $this->reverseCleanClassName($theme);
  5434. $array = $this->getThemesGithub();
  5435. $arrayLower = array_change_key_case($array);
  5436. if (!$array) {
  5437. $this->setAPIResponse('error', 'Could not access theme marketplace', 409);
  5438. return false;
  5439. }
  5440. if (!$arrayLower[$theme]) {
  5441. $this->setAPIResponse('error', 'Theme does not exist in marketplace', 404);
  5442. return false;
  5443. } else {
  5444. $key = array_search($theme, array_keys($arrayLower));
  5445. $theme = array_keys($array)[$key];
  5446. }
  5447. $array = $array[$theme];
  5448. $downloadList = $this->marketplaceFileListFormat($array['files'], $array['github_folder'], 'themes');
  5449. if (!$downloadList) {
  5450. $this->setAPIResponse('error', 'Could not get download list for theme', 409);
  5451. return false;
  5452. }
  5453. $name = $theme;
  5454. $version = $array['version'];
  5455. foreach ($downloadList as $k => $v) {
  5456. $file = array(
  5457. 'from' => $v['githubPath'],
  5458. 'to' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'] . $v['fileName']),
  5459. 'path' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'])
  5460. );
  5461. if (!$this->downloadFileToPath($file['from'], $file['to'], $file['path'])) {
  5462. $this->writeLog('error', 'Theme Function - Downloaded File Failed for: ' . $v['githubPath'], $this->user['username']);
  5463. $this->setAPIResponse('error', 'Theme download failed', 500);
  5464. return false;
  5465. }
  5466. }
  5467. if ($this->config['installedThemes'] !== '') {
  5468. $installedThemes = explode('|', $this->config['installedThemes']);
  5469. foreach ($installedThemes as $k => $v) {
  5470. $themes = explode(':', $v);
  5471. $installedThemesList[$themes[0]] = $themes[1];
  5472. }
  5473. if (isset($installedThemesList[$name])) {
  5474. $installedThemesList[$name] = $version;
  5475. $installedThemesNew = '';
  5476. foreach ($installedThemesList as $k => $v) {
  5477. if ($installedThemesNew == '') {
  5478. $installedThemesNew .= $k . ':' . $v;
  5479. } else {
  5480. $installedThemesNew .= '|' . $k . ':' . $v;
  5481. }
  5482. }
  5483. } else {
  5484. $installedThemesNew = $this->config['installedThemes'] . '|' . $name . ':' . $version;
  5485. }
  5486. } else {
  5487. $installedThemesNew = $name . ':' . $version;
  5488. }
  5489. $this->updateConfig(array('installedThemes' => $installedThemesNew));
  5490. $this->setAPIResponse('success', 'Theme installed', 200, $installedThemesNew);
  5491. return true;
  5492. }
  5493. public function removePlugin($plugin)
  5494. {
  5495. $plugin = $this->reverseCleanClassName($plugin);
  5496. $array = $this->getPluginsGithub();
  5497. $arrayLower = array_change_key_case($array);
  5498. if (!$array) {
  5499. $this->setAPIResponse('error', 'Could not access plugin marketplace', 409);
  5500. return false;
  5501. }
  5502. if (!$arrayLower[$plugin]) {
  5503. $this->setAPIResponse('error', 'Plugin does not exist in marketplace', 404);
  5504. return false;
  5505. } else {
  5506. $key = array_search($plugin, array_keys($arrayLower));
  5507. $plugin = array_keys($array)[$key];
  5508. }
  5509. $array = $array[$plugin];
  5510. $name = $plugin;
  5511. $version = $array['version'];
  5512. $installedPluginsNew = '';
  5513. $pluginDir = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . $array['github_folder'] . DIRECTORY_SEPARATOR;
  5514. $dirExists = file_exists($pluginDir);
  5515. if ($dirExists) {
  5516. if (!$this->rrmdir($pluginDir)) {
  5517. $this->writeLog('error', 'Plugin Function - Remove File Failed for: ' . $array['github_folder'], $this->user['username']);
  5518. return false;
  5519. }
  5520. } else {
  5521. $this->setAPIResponse('error', 'Plugin is not installed', 404);
  5522. return false;
  5523. }
  5524. if ($this->config['installedPlugins'] !== '') {
  5525. $installedPlugins = explode('|', $this->config['installedPlugins']);
  5526. foreach ($installedPlugins as $k => $v) {
  5527. $plugins = explode(':', $v);
  5528. $installedPluginsList[$plugins[0]] = $plugins[1];
  5529. }
  5530. if (isset($installedPluginsList[$name])) {
  5531. foreach ($installedPluginsList as $k => $v) {
  5532. if ($k !== $name) {
  5533. if ($installedPluginsNew == '') {
  5534. $installedPluginsNew .= $k . ':' . $v;
  5535. } else {
  5536. $installedPluginsNew .= '|' . $k . ':' . $v;
  5537. }
  5538. }
  5539. }
  5540. }
  5541. }
  5542. $this->updateConfig(array('installedPlugins' => $installedPluginsNew));
  5543. $this->setAPIResponse('success', 'Plugin removed', 200, $installedPluginsNew);
  5544. return true;
  5545. }
  5546. public function pluginFileListFormat($files, $folder)
  5547. {
  5548. $filesList = false;
  5549. foreach ($files as $k => $v) {
  5550. $filesList[] = array(
  5551. 'fileName' => $v['name'],
  5552. 'path' => DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR,
  5553. 'githubPath' => $v['download_url']
  5554. );
  5555. }
  5556. return $filesList;
  5557. }
  5558. public function getPluginFilesFromGithub($plugin = 'test')
  5559. {
  5560. $url = 'https://api.github.com/repos/causefx/organizr/contents/' . $plugin . '?ref=v2-plugins';
  5561. $options = array('verify' => false);
  5562. $response = Requests::get($url, array(), $options);
  5563. if ($response->success) {
  5564. return json_decode($response->body, true);
  5565. }
  5566. return false;
  5567. }
  5568. public function installPlugin($plugin)
  5569. {
  5570. $plugin = $this->reverseCleanClassName($plugin);
  5571. $array = $this->getPluginsGithub();
  5572. $arrayLower = array_change_key_case($array);
  5573. if (!$array) {
  5574. $this->setAPIResponse('error', 'Could not access plugin marketplace', 409);
  5575. return false;
  5576. }
  5577. if (!$arrayLower[$plugin]) {
  5578. $this->setAPIResponse('error', 'Plugin does not exist in marketplace', 404);
  5579. return false;
  5580. } else {
  5581. $key = array_search($plugin, array_keys($arrayLower));
  5582. $plugin = array_keys($array)[$key];
  5583. }
  5584. $array = $array[$plugin];
  5585. // Check Version of Organizr against minimum version needed
  5586. $compare = new Composer\Semver\Comparator;
  5587. if (!$compare->lessThan($array['minimum_organizr_version'], $this->version)) {
  5588. $this->setResponse(500, 'Minimum Organizr version needed: ' . $array['minimum_organizr_version']);
  5589. return true;
  5590. }
  5591. $files = $this->getPluginFilesFromGithub($array['github_folder']);
  5592. if ($files) {
  5593. $downloadList = $this->pluginFileListFormat($files, $array['github_folder']);
  5594. } else {
  5595. $this->writeLog('error', 'Plugin Function - Downloaded File Failed for: ' . $array['github_folder'], $this->user['username']);
  5596. $this->setAPIResponse('error', 'Could not get download list for plugin', 409);
  5597. return false;
  5598. }
  5599. if (!$downloadList) {
  5600. $this->setAPIResponse('error', 'Could not get download list for plugin', 409);
  5601. return false;
  5602. }
  5603. $name = $plugin;
  5604. $version = $array['version'];
  5605. foreach ($downloadList as $k => $v) {
  5606. $file = array(
  5607. 'from' => $v['githubPath'],
  5608. 'to' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'] . $v['fileName']),
  5609. 'path' => str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->root . $v['path'])
  5610. );
  5611. if (!$this->downloadFileToPath($file['from'], $file['to'], $file['path'])) {
  5612. $this->writeLog('error', 'Plugin Function - Downloaded File Failed for: ' . $v['githubPath'], $this->user['username']);
  5613. $this->setAPIResponse('error', 'Plugin download failed', 500);
  5614. return false;
  5615. }
  5616. }
  5617. if ($this->config['installedPlugins'] !== '') {
  5618. $installedPlugins = explode('|', $this->config['installedPlugins']);
  5619. foreach ($installedPlugins as $k => $v) {
  5620. $plugins = explode(':', $v);
  5621. $installedPluginsList[$plugins[0]] = $plugins[1];
  5622. }
  5623. if (isset($installedPluginsList[$name])) {
  5624. $installedPluginsList[$name] = $version;
  5625. $installedPluginsNew = '';
  5626. foreach ($installedPluginsList as $k => $v) {
  5627. if ($installedPluginsNew == '') {
  5628. $installedPluginsNew .= $k . ':' . $v;
  5629. } else {
  5630. $installedPluginsNew .= '|' . $k . ':' . $v;
  5631. }
  5632. }
  5633. } else {
  5634. $installedPluginsNew = $this->config['installedPlugins'] . '|' . $name . ':' . $version;
  5635. }
  5636. } else {
  5637. $installedPluginsNew = $name . ':' . $version;
  5638. }
  5639. $this->updateConfig(array('installedPlugins' => $installedPluginsNew));
  5640. $this->setAPIResponse('success', 'Plugin installed', 200, $installedPluginsNew);
  5641. return true;
  5642. }
  5643. public function getThemesGithub()
  5644. {
  5645. $url = 'https://raw.githubusercontent.com/causefx/Organizr/v2-themes/themes.json';
  5646. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  5647. $response = Requests::get($url, array(), $options);
  5648. if ($response->success) {
  5649. return json_decode($response->body, true);
  5650. }
  5651. return false;
  5652. }
  5653. public function getPluginsGithub()
  5654. {
  5655. $url = 'https://raw.githubusercontent.com/causefx/Organizr/v2-plugins/plugins.json';
  5656. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  5657. try {
  5658. $response = Requests::get($url, array(), $options);
  5659. if ($response->success) {
  5660. return json_decode($response->body, true);
  5661. }
  5662. } catch (Requests_Exception $e) {
  5663. return false;
  5664. }
  5665. return false;
  5666. }
  5667. public function getOpenCollectiveBackers()
  5668. {
  5669. $url = 'https://opencollective.com/organizr/members/users.json?limit=100&offset=0';
  5670. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  5671. try {
  5672. $response = Requests::get($url, array(), $options);
  5673. if ($response->success) {
  5674. $api = json_decode($response->body, true);
  5675. foreach ($api as $k => $backer) {
  5676. $api[$k] = array_merge($api[$k], ['sortName' => strtolower($backer['name'])]);
  5677. }
  5678. $this->setAPIResponse('success', '', 200, $api);
  5679. return $api;
  5680. }
  5681. } catch (Requests_Exception $e) {
  5682. $this->setAPIResponse('error', $e->getMessage(), 500);
  5683. return false;
  5684. }
  5685. $this->setAPIResponse('error', 'Error connecting to Open Collective', 409);
  5686. return false;
  5687. }
  5688. public function getGithubSponsors()
  5689. {
  5690. $url = 'https://github.com/sponsors/causefx';
  5691. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  5692. $response = Requests::get($url, array(), $options);
  5693. if ($response->success) {
  5694. $sponsors = [];
  5695. $dom = new PHPHtmlParser\Dom;
  5696. try {
  5697. $dom->loadStr($response->body);
  5698. $contents = $dom->find('#sponsors .clearfix div');
  5699. foreach ($contents as $content) {
  5700. $html = $content->innerHtml;
  5701. preg_match('/(@[a-zA-Z])\w+/', $html, $username);
  5702. preg_match('/(?i)\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'\".,<>?«»""\'\']))/', $html, $image);
  5703. if (isset($image[0]) && isset($username[0])) {
  5704. $sponsors[] = [
  5705. 'name' => str_replace('@', '', $username[0]),
  5706. 'sortName' => str_replace('@', '', strtolower($username[0])),
  5707. 'image' => str_replace('s=60', 's=200', $image[0]),
  5708. 'isActive' => true,
  5709. 'type' => 'USER',
  5710. 'role' => 'BACKER'
  5711. ];
  5712. }
  5713. }
  5714. $this->setAPIResponse('success', '', 200, $sponsors);
  5715. return $sponsors;
  5716. } catch (\PHPHtmlParser\Exceptions\ChildNotFoundException | \PHPHtmlParser\Exceptions\CircularException | \PHPHtmlParser\Exceptions\LogicalException | \PHPHtmlParser\Exceptions\StrictException | \PHPHtmlParser\Exceptions\ContentLengthException | \PHPHtmlParser\Exceptions\NotLoadedException $e) {
  5717. $this->setAPIResponse('error', 'Error connecting to Github', 409);
  5718. return false;
  5719. }
  5720. }
  5721. $this->setAPIResponse('error', 'Error connecting to Github', 409);
  5722. return false;
  5723. }
  5724. public function getAllSponsors()
  5725. {
  5726. $sponsors = [];
  5727. $list = [
  5728. 'openCollective' => $this->getOpenCollectiveBackers(),
  5729. 'github' => $this->getGithubSponsors()
  5730. ];
  5731. foreach ($list as $k => $sponsor) {
  5732. if ($sponsor) {
  5733. $sponsors = array_merge($sponsor, $sponsors);
  5734. }
  5735. }
  5736. if ($sponsors) {
  5737. usort($sponsors, function ($a, $b) {
  5738. return $a['sortName'] <=> $b['sortName'];
  5739. });
  5740. }
  5741. $this->setAPIResponse('success', '', 200, $sponsors);
  5742. return $sponsors;
  5743. }
  5744. public function getOrganizrSmtpFromAPI()
  5745. {
  5746. $url = 'https://api.organizr.app/?cmd=smtp';
  5747. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  5748. try {
  5749. $response = Requests::get($url, array(), $options);
  5750. if ($response->success) {
  5751. return json_decode($response->body, true);
  5752. }
  5753. } catch (Requests_Exception $e) {
  5754. $this->setAPIResponse('error', $e->getMessage(), 500);
  5755. return false;
  5756. }
  5757. return false;
  5758. }
  5759. public function saveOrganizrSmtpFromAPI()
  5760. {
  5761. $api = $this->getOrganizrSmtpFromAPI();
  5762. if ($api) {
  5763. $this->updateConfigItems($api['response']['data']);
  5764. $this->setAPIResponse(null, 'SMTP activated with Organizr SMTP account');
  5765. return true;
  5766. } else {
  5767. return false;
  5768. }
  5769. }
  5770. public function guestHash($start, $end)
  5771. {
  5772. $ip = $this->userIP();
  5773. $ip = md5($ip);
  5774. return substr($ip, $start, $end);
  5775. }
  5776. public function rrmdir($dir)
  5777. {
  5778. ini_set('max_execution_time', 0);
  5779. set_time_limit(0);
  5780. if (is_dir($dir)) {
  5781. $files = scandir($dir);
  5782. foreach ($files as $file) {
  5783. if ($file != "." && $file != "..") {
  5784. $this->rrmdir("$dir/$file");
  5785. }
  5786. }
  5787. rmdir($dir);
  5788. } elseif (file_exists($dir)) {
  5789. unlink($dir);
  5790. }
  5791. return true;
  5792. }
  5793. public function rcopy($src, $dst)
  5794. {
  5795. ini_set('max_execution_time', 0);
  5796. set_time_limit(0);
  5797. $src = $this->cleanPath($src);
  5798. $dst = $this->cleanPath($dst);
  5799. if (is_dir($src)) {
  5800. if (!file_exists($dst)) : mkdir($dst);
  5801. endif;
  5802. $files = scandir($src);
  5803. foreach ($files as $file) {
  5804. if ($file != "." && $file != "..") {
  5805. $this->rcopy("$src/$file", "$dst/$file");
  5806. }
  5807. }
  5808. } elseif (file_exists($src)) {
  5809. copy($src, $dst);
  5810. }
  5811. return true;
  5812. }
  5813. public function unzipFile($zipFile)
  5814. {
  5815. ini_set('max_execution_time', 0);
  5816. set_time_limit(0);
  5817. $zip = new ZipArchive;
  5818. $extractPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . "upgrade/";
  5819. if ($zip->open($extractPath . $zipFile) != "true") {
  5820. $this->writeLog("error", "organizr could not unzip upgrade.zip");
  5821. } else {
  5822. $this->writeLog("success", "organizr unzipped upgrade.zip");
  5823. }
  5824. /* Extract Zip File */
  5825. $zip->extractTo($extractPath);
  5826. $zip->close();
  5827. return true;
  5828. }
  5829. public function downloadFile($url, $path)
  5830. {
  5831. ini_set('max_execution_time', 0);
  5832. set_time_limit(0);
  5833. $folderPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . "upgrade" . DIRECTORY_SEPARATOR;
  5834. if (!file_exists($folderPath)) {
  5835. if (@!mkdir($folderPath)) {
  5836. $this->writeLog('error', 'Update Function - Folder Creation failed', $this->user['username']);
  5837. return false;
  5838. }
  5839. }
  5840. $newfname = $folderPath . $path;
  5841. $context = stream_context_create(
  5842. array(
  5843. 'ssl' => array(
  5844. 'verify_peer' => true,
  5845. 'cafile' => $this->getCert()
  5846. )
  5847. )
  5848. );
  5849. $file = fopen($url, 'rb', false, $context);
  5850. if ($file) {
  5851. $newf = fopen($newfname, 'wb');
  5852. if ($newf) {
  5853. while (!feof($file)) {
  5854. fwrite($newf, fread($file, 1024 * 8), 1024 * 8);
  5855. }
  5856. }
  5857. } else {
  5858. $this->writeLog("error", "organizr could not download $url");
  5859. return false;
  5860. }
  5861. if ($file) {
  5862. fclose($file);
  5863. $this->writeLog("success", "organizr finished downloading the github zip file");
  5864. } else {
  5865. $this->writeLog("error", "organizr could not download the github zip file");
  5866. return false;
  5867. }
  5868. if ($newf) {
  5869. fclose($newf);
  5870. $this->writeLog("success", "organizr created upgrade zip file from github zip file");
  5871. } else {
  5872. $this->writeLog("error", "organizr could not create upgrade zip file from github zip file");
  5873. return false;
  5874. }
  5875. return true;
  5876. }
  5877. public function downloadFileToPath($from, $to, $path)
  5878. {
  5879. ini_set('max_execution_time', 0);
  5880. set_time_limit(0);
  5881. if (@!mkdir($path, 0777, true)) {
  5882. $this->writeLog("error", "organizr could not create folder or folder already exists", 'SYSTEM');
  5883. }
  5884. $file = fopen($from, 'rb');
  5885. if ($file) {
  5886. $newf = fopen($to, 'wb');
  5887. if ($newf) {
  5888. while (!feof($file)) {
  5889. fwrite($newf, fread($file, 1024 * 8), 1024 * 8);
  5890. }
  5891. }
  5892. } else {
  5893. $this->writeLog("error", "organizr could not download file", 'SYSTEM');
  5894. }
  5895. if ($file) {
  5896. fclose($file);
  5897. $this->writeLog("success", "organizr finished downloading the file", 'SYSTEM');
  5898. } else {
  5899. $this->writeLog("error", "organizr could not download the file", 'SYSTEM');
  5900. }
  5901. if ($newf) {
  5902. fclose($newf);
  5903. $this->writeLog("success", "organizr saved/moved the file", 'SYSTEM');
  5904. } else {
  5905. $this->writeLog("error", "organizr could not saved/moved the file", 'SYSTEM');
  5906. }
  5907. return true;
  5908. }
  5909. public function getAllUsers($includeGroups = false)
  5910. {
  5911. $response = [
  5912. array(
  5913. 'function' => 'fetchAll',
  5914. 'query' => array(
  5915. 'SELECT * FROM users'
  5916. ),
  5917. 'key' => 'users'
  5918. ),
  5919. ];
  5920. $groups = array(
  5921. 'function' => 'fetchAll',
  5922. 'query' => array(
  5923. 'SELECT * FROM groups ORDER BY group_id ASC'
  5924. ),
  5925. 'key' => 'groups'
  5926. );
  5927. $addGroups = (isset($_GET['includeGroups']) || $includeGroups) ?? false;
  5928. if ($addGroups) {
  5929. array_push($response, $groups);
  5930. }
  5931. return $this->processQueries($response);
  5932. }
  5933. public function getAllGroups()
  5934. {
  5935. $response = [
  5936. array(
  5937. 'function' => 'fetchAll',
  5938. 'query' => array(
  5939. 'SELECT * FROM groups ORDER BY group_id ASC'
  5940. ),
  5941. 'key' => 'groups'
  5942. ),
  5943. ];
  5944. $users = array(
  5945. 'function' => 'fetchAll',
  5946. 'query' => array(
  5947. 'SELECT * FROM users'
  5948. ),
  5949. 'key' => 'users'
  5950. );
  5951. $addUsers = (isset($_GET['includeUsers'])) ?? false;
  5952. if ($addUsers) {
  5953. array_push($response, $users);
  5954. }
  5955. return $this->processQueries($response);
  5956. }
  5957. public function importUsers($array)
  5958. {
  5959. $imported = 0;
  5960. foreach ($array as $user) {
  5961. $password = $this->random_ascii_string(30);
  5962. if ($user['username'] !== '' && $user['email'] !== '' && $password !== '') {
  5963. $newUser = $this->createUser($user['username'], $password, $user['email']);
  5964. if (!$newUser) {
  5965. $this->writeLog('error', 'Import Function - Error', $user['username']);
  5966. } else {
  5967. $imported++;
  5968. }
  5969. }
  5970. }
  5971. $this->setAPIResponse('success', 'Imported ' . $imported . ' users', 200);
  5972. return true;
  5973. }
  5974. public function importUsersType($type)
  5975. {
  5976. if ($type !== '') {
  5977. switch ($type) {
  5978. case 'plex':
  5979. return $this->importUsers($this->allPlexUsers(true));
  5980. case 'jellyfin':
  5981. return $this->importUsers($this->allJellyfinUsers(true));
  5982. case 'emby':
  5983. return $this->importUsers($this->allEmbyUsers(true));
  5984. default:
  5985. return false;
  5986. }
  5987. }
  5988. return false;
  5989. }
  5990. public function allPlexUsers($newOnly = false, $friendsOnly = false)
  5991. {
  5992. try {
  5993. if (!empty($this->config['plexToken'])) {
  5994. $url = 'https://plex.tv/api/users';
  5995. $headers = array(
  5996. 'X-Plex-Token' => $this->config['plexToken'],
  5997. );
  5998. $response = Requests::get($url, $headers);
  5999. if ($response->success) {
  6000. libxml_use_internal_errors(true);
  6001. $userXML = simplexml_load_string($response->body);
  6002. if (is_array($userXML) || is_object($userXML)) {
  6003. $results = array();
  6004. foreach ($userXML as $child) {
  6005. if (((string)$child['restricted'] == '0')) {
  6006. if ($newOnly) {
  6007. $taken = $this->usernameTaken((string)$child['username'], (string)$child['email']);
  6008. if (!$taken) {
  6009. $results[] = array(
  6010. 'username' => (string)$child['username'],
  6011. 'email' => (string)$child['email'],
  6012. 'id' => (string)$child['id'],
  6013. );
  6014. }
  6015. } elseif ($friendsOnly) {
  6016. $machineMatches = false;
  6017. foreach ($child->Server as $server) {
  6018. if ((string)$server['machineIdentifier'] == $this->config['plexID']) {
  6019. $machineMatches = true;
  6020. $shareId = $server['id'];
  6021. }
  6022. }
  6023. if ($machineMatches) {
  6024. $results[] = array(
  6025. 'username' => (string)$child['username'],
  6026. 'email' => (string)$child['email'],
  6027. 'id' => (string)$child['id'],
  6028. 'shareId' => (string)$shareId
  6029. );
  6030. }
  6031. } else {
  6032. $results[] = array(
  6033. 'username' => (string)$child['username'],
  6034. 'email' => (string)$child['email'],
  6035. 'id' => (string)$child['id'],
  6036. );
  6037. }
  6038. }
  6039. }
  6040. return $results;
  6041. }
  6042. }
  6043. }
  6044. return false;
  6045. } catch (Requests_Exception $e) {
  6046. $this->writeLog('success', 'Plex Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
  6047. }
  6048. return false;
  6049. }
  6050. public function allJellyfinUsers($newOnly = false)
  6051. {
  6052. try {
  6053. if (!empty($this->config['jellyfinURL']) && !empty($this->config['jellyfinToken'])) {
  6054. $url = $this->qualifyURL($this->config['jellyfinURL']) . '/Users?api_key=' . $this->config['jellyfinToken'];
  6055. $headers = array();
  6056. $response = Requests::get($url, $headers);
  6057. if ($response->success) {
  6058. $users = json_decode($response->body, true);
  6059. if (is_array($users) || is_object($users)) {
  6060. $results = array();
  6061. foreach ($users as $child) {
  6062. // Jellyfin doesn't list emails for some reason
  6063. $email = $this->random_ascii_string(10) . '@placeholder.eml';
  6064. if ($newOnly) {
  6065. $taken = $this->usernameTaken((string)$child['Name'], $email);
  6066. if (!$taken) {
  6067. $results[] = array(
  6068. 'username' => (string)$child['Name'],
  6069. 'email' => $email
  6070. );
  6071. }
  6072. } else {
  6073. $results[] = array(
  6074. 'username' => (string)$child['Name'],
  6075. 'email' => $email,
  6076. );
  6077. }
  6078. }
  6079. return $results;
  6080. }
  6081. }
  6082. }
  6083. return false;
  6084. } catch (Requests_Exception $e) {
  6085. $this->writeLog('success', 'Jellyfin Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
  6086. }
  6087. return false;
  6088. }
  6089. public function allEmbyUsers($newOnly = false)
  6090. {
  6091. try {
  6092. if (!empty($this->config['embyURL']) && !empty($this->config['embyToken'])) {
  6093. $url = $this->qualifyURL($this->config['embyURL']) . '/Users?api_key=' . $this->config['embyToken'];
  6094. $headers = array();
  6095. $response = Requests::get($url, $headers);
  6096. if ($response->success) {
  6097. $users = json_decode($response->body, true);
  6098. if (is_array($users) || is_object($users)) {
  6099. $results = array();
  6100. foreach ($users as $child) {
  6101. // Emby doesn't list emails for some reason
  6102. $email = $this->random_ascii_string(10) . '@placeholder.eml';
  6103. if ($newOnly) {
  6104. $taken = $this->usernameTaken((string)$child['Name'], $email);
  6105. if (!$taken) {
  6106. $results[] = array(
  6107. 'username' => (string)$child['Name'],
  6108. 'email' => $email
  6109. );
  6110. }
  6111. } else {
  6112. $results[] = array(
  6113. 'username' => (string)$child['Name'],
  6114. 'email' => $email,
  6115. );
  6116. }
  6117. }
  6118. return $results;
  6119. }
  6120. }
  6121. }
  6122. return false;
  6123. } catch (Requests_Exception $e) {
  6124. $this->writeLog('success', 'Emby Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
  6125. }
  6126. return false;
  6127. }
  6128. public function updateUser($id, $array)
  6129. {
  6130. if (!$id) {
  6131. $this->setAPIResponse('error', 'Id was not supplied', 422);
  6132. return false;
  6133. }
  6134. if ((int)$id !== $this->user['userID']) {
  6135. if (!$this->qualifyRequest('1', true)) {
  6136. return false;
  6137. }
  6138. }
  6139. $user = $this->getUserById($id);
  6140. if ($user) {
  6141. $array = $this->checkKeys($user, $array);
  6142. } else {
  6143. $this->setAPIResponse('error', 'User was not found', 404);
  6144. return false;
  6145. }
  6146. if ($user['group_id'] == 0 && $this->user['groupID'] !== 0) {
  6147. $this->setAPIResponse('error', 'Cannot update admin unless you are admin', 401);
  6148. return false;
  6149. }
  6150. if (array_key_exists('username', $array)) {
  6151. if ($array['username'] == '') {
  6152. $this->setAPIResponse('error', 'Username was set but empty', 409);
  6153. return false;
  6154. }
  6155. if ($this->usernameTaken($array['username'], $array['username'], $id)) {
  6156. $this->setAPIResponse('error', 'Username: ' . $array['username'] . ' is already taken', 409);
  6157. return false;
  6158. }
  6159. }
  6160. if (array_key_exists('email', $array)) {
  6161. if ($array['email'] == '') {
  6162. $this->setAPIResponse('error', 'Email was set but empty', 409);
  6163. return false;
  6164. }
  6165. if ($this->usernameTaken($array['email'], $array['email'], $id)) {
  6166. $this->setAPIResponse('error', 'Email: ' . $array['email'] . ' is already taken', 409);
  6167. return false;
  6168. }
  6169. }
  6170. if (array_key_exists('group_id', $array)) {
  6171. if ($array['group_id'] == '') {
  6172. $array['group_id'] = 0;
  6173. //$this->setAPIResponse('error', 'group_id was set but empty', 409);
  6174. //return false;
  6175. }
  6176. if (!$this->qualifyRequest('1', false)) {
  6177. $this->setAPIResponse('error', 'Cannot change your own group_id', 401);
  6178. return false;
  6179. }
  6180. if (($id == $this->user['userID']) && $this->user['groupID'] == 0) {
  6181. $array['group_id'] = 0;
  6182. }
  6183. if (($id == $this->user['userID']) && ($array['group_id'] == 0 && $this->user['groupID'] !== 0)) {
  6184. $this->setAPIResponse('error', 'Only admins can make others admins', 401);
  6185. return false;
  6186. }
  6187. $array['group'] = $this->getGroupByGroupId($array['group_id'])['group'];
  6188. if (!$array['group']) {
  6189. $this->setAPIResponse('error', 'group_id does not exist', 404);
  6190. return false;
  6191. }
  6192. }
  6193. if (array_key_exists('locked', $array)) {
  6194. //$this->setAPIResponse('error', 'Cannot use endpoint to unlock or lock user - please use /users/{id}/lock', 409);
  6195. //return false;
  6196. }
  6197. if (array_key_exists('password', $array)) {
  6198. if ($array['password'] == '') {
  6199. $this->setAPIResponse('error', 'Password was set but empty', 409);
  6200. return false;
  6201. }
  6202. $array['password'] = password_hash($array['password'], PASSWORD_BCRYPT);
  6203. }
  6204. if (array_key_exists('register_date', $array)) {
  6205. $this->setAPIResponse('error', 'Cannot update register date', 409);
  6206. return false;
  6207. }
  6208. $response = [
  6209. array(
  6210. 'function' => 'query',
  6211. 'query' => array(
  6212. 'UPDATE users SET',
  6213. $array,
  6214. 'WHERE id = ?',
  6215. $id
  6216. )
  6217. ),
  6218. ];
  6219. $this->setAPIResponse(null, 'User info updated');
  6220. $this->writeLog('success', 'User Editor Function - Updated User Info for [' . $user['username'] . ']', $this->user['username']);
  6221. return $this->processQueries($response);
  6222. }
  6223. public function deleteUser($id)
  6224. {
  6225. $response = [
  6226. array(
  6227. 'function' => 'query',
  6228. 'query' => array(
  6229. 'DELETE FROM users WHERE id = ?',
  6230. $id
  6231. )
  6232. ),
  6233. ];
  6234. $userInfo = $this->getUserById($id);
  6235. if ($id == $this->user['userID']) {
  6236. $this->setAPIResponse('error', 'Cannot delete your own user', 409);
  6237. return false;
  6238. }
  6239. if ($userInfo) {
  6240. $this->writeLog('success', 'User Delete Function - Deleted User [' . $userInfo['username'] . ']', $this->user['username']);
  6241. $this->setAPIResponse('success', 'User deleted', 204);
  6242. return $this->processQueries($response);
  6243. } else {
  6244. $this->setAPIResponse('error', 'id not found', 404);
  6245. return false;
  6246. }
  6247. }
  6248. public function addUser($array)
  6249. {
  6250. $username = $array['username'] ?? null;
  6251. $password = $array['password'] ?? null;
  6252. $email = $array['email'] ?? null;
  6253. if (!$username) {
  6254. $this->setAPIResponse('error', 'Username was not supplied', 409);
  6255. return false;
  6256. }
  6257. if (!$password) {
  6258. $this->setAPIResponse('error', 'Password was not supplied', 409);
  6259. return false;
  6260. }
  6261. if ($this->createUser($username, $password, $email)) {
  6262. $this->writeLog('success', 'Create User Function - Account created for [' . $username . ']', $this->user['username']);
  6263. return true;
  6264. } else {
  6265. $this->writeLog('error', 'Create User Function - An error occurred', $this->user['username']);
  6266. return false;
  6267. }
  6268. }
  6269. public function createUser($username, $password, $email = null)
  6270. {
  6271. $username = $username ?? null;
  6272. $password = $password ?? null;
  6273. $email = ($email) ? $email : $this->random_ascii_string(10) . '@placeholder.eml';
  6274. if (!$username) {
  6275. $this->setAPIResponse('error', 'Username was set but empty', 409);
  6276. return false;
  6277. }
  6278. if (!$password) {
  6279. $this->setAPIResponse('error', 'Password was set but empty', 409);
  6280. return false;
  6281. }
  6282. if ($this->usernameTaken($username, $email)) {
  6283. $this->setAPIResponse('error', 'Username: ' . $username . ' or Email: ' . $email . ' is already taken', 409);
  6284. return false;
  6285. }
  6286. $defaults = $this->getDefaultGroup();
  6287. $userInfo = [
  6288. 'username' => $username,
  6289. 'password' => password_hash($password, PASSWORD_BCRYPT),
  6290. 'email' => $email,
  6291. 'group' => $defaults['group'],
  6292. 'group_id' => $defaults['group_id'],
  6293. 'image' => $this->gravatar($email),
  6294. 'register_date' => $this->currentTime,
  6295. ];
  6296. $response = [
  6297. array(
  6298. 'function' => 'query',
  6299. 'query' => array(
  6300. 'INSERT INTO [users]',
  6301. $userInfo
  6302. )
  6303. ),
  6304. ];
  6305. $this->setAPIResponse('success', 'User created', 200);
  6306. return $this->processQueries($response);
  6307. }
  6308. public function updateGroup($id, $array)
  6309. {
  6310. if (!$id || $id == '') {
  6311. $this->setAPIResponse('error', 'id was not set', 422);
  6312. return null;
  6313. }
  6314. if (!$array) {
  6315. $this->setAPIResponse('error', 'no data was sent', 422);
  6316. return null;
  6317. }
  6318. $groupInfo = $this->getGroupById($id);
  6319. if ($groupInfo) {
  6320. $array = $this->checkKeys($groupInfo, $array);
  6321. } else {
  6322. $this->setAPIResponse('error', 'No category info found', 404);
  6323. return false;
  6324. }
  6325. if (array_key_exists('group_id', $array)) {
  6326. $this->setAPIResponse('error', 'Cannot change group_id', 409);
  6327. return false;
  6328. }
  6329. if (array_key_exists('group', $array)) {
  6330. if ($array['group'] == '') {
  6331. $this->setAPIResponse('error', 'Group was set but empty', 409);
  6332. return false;
  6333. }
  6334. if ($this->isGroupNameTaken($array['group'], $id)) {
  6335. $this->setAPIResponse('error', 'Group name: ' . $array['group'] . ' is already taken', 409);
  6336. return false;
  6337. }
  6338. }
  6339. if (array_key_exists('image', $array)) {
  6340. if ($array['image'] == '') {
  6341. $this->setAPIResponse('error', 'Image was set but empty', 409);
  6342. return false;
  6343. }
  6344. }
  6345. if (array_key_exists('default', $array)) {
  6346. if ($groupInfo['group_id'] == 0 || $groupInfo['group_id'] == 999) {
  6347. $this->setAPIResponse('error', 'Setting ' . $groupInfo['group'] . ' as default group is not allowed', 409);
  6348. return false;
  6349. }
  6350. if ($array['default']) {
  6351. $this->clearGroupDefault();
  6352. $array['default'] = 1;
  6353. }
  6354. }
  6355. $response = [
  6356. array(
  6357. 'function' => 'query',
  6358. 'query' => array(
  6359. 'UPDATE groups SET',
  6360. $array,
  6361. 'WHERE id = ?',
  6362. $id
  6363. )
  6364. ),
  6365. ];
  6366. $this->setAPIResponse(null, 'Group info updated');
  6367. $this->writeLog('success', 'Group Editor Function - Edited Group Info for [' . $groupInfo['group'] . ']', $this->user['username']);
  6368. return $this->processQueries($response);
  6369. }
  6370. public function deleteGroup($id)
  6371. {
  6372. $response = [
  6373. array(
  6374. 'function' => 'query',
  6375. 'query' => array(
  6376. 'DELETE FROM groups WHERE id = ?',
  6377. $id
  6378. )
  6379. ),
  6380. ];
  6381. $groupInfo = $this->getGroupById($id);
  6382. if ($groupInfo['group_id'] == 0 || $groupInfo['group_id'] == 999) {
  6383. $this->setAPIResponse('error', 'Cannot delete group: ' . $groupInfo['group'] . ' as it is not allowed', 409);
  6384. return false;
  6385. }
  6386. if ($this->getGroupUserCountById($id) >= 1) {
  6387. $this->setAPIResponse('error', 'Cannot delete group as group still has users assigned to it', 409);
  6388. return false;
  6389. }
  6390. if ($groupInfo) {
  6391. $this->writeLog('success', 'Group Delete Function - Deleted Group [' . $groupInfo['group'] . ']', $this->user['username']);
  6392. $this->setAPIResponse('success', 'Group deleted', 204);
  6393. return $this->processQueries($response);
  6394. } else {
  6395. $this->setAPIResponse('error', 'id not found', 404);
  6396. return false;
  6397. }
  6398. }
  6399. public function addGroup($array)
  6400. {
  6401. if (!$array) {
  6402. $this->setAPIResponse('error', 'no data was sent', 422);
  6403. return null;
  6404. }
  6405. $array = $this->checkKeys($this->getTableColumnsFormatted('groups'), $array);
  6406. $array['default'] = ($array['default']) ?? 0;
  6407. $array['group_id'] = $this->getNextGroupOrder() + 1;
  6408. if (array_key_exists('group', $array)) {
  6409. if ($this->isGroupNameTaken($array['group'])) {
  6410. $this->setAPIResponse('error', 'Group name: ' . $array['group'] . ' is already taken', 409);
  6411. return false;
  6412. }
  6413. } else {
  6414. $this->setAPIResponse('error', 'Group name was not supplied', 422);
  6415. return false;
  6416. }
  6417. if (array_key_exists('image', $array)) {
  6418. if ($array['image'] == '') {
  6419. $this->setAPIResponse('error', 'Group image cannot be empty', 422);
  6420. return false;
  6421. }
  6422. } else {
  6423. $this->setAPIResponse('error', 'Group image was not supplied', 422);
  6424. return false;
  6425. }
  6426. $response = [
  6427. array(
  6428. 'function' => 'query',
  6429. 'query' => array(
  6430. 'INSERT INTO [groups]',
  6431. $array
  6432. )
  6433. ),
  6434. ];
  6435. $this->setAPIResponse(null, 'Group added');
  6436. $this->writeLog('success', 'Group Editor Function - Added Group for [' . $array['group'] . ']', $this->user['username']);
  6437. return $this->processQueries($response);
  6438. }
  6439. public function userList($type = null)
  6440. {
  6441. switch ($type) {
  6442. case 'plex':
  6443. if (!empty($this->config['plexToken']) && !empty($this->config['plexID'])) {
  6444. $url = 'https://plex.tv/api/servers/' . $this->config['plexID'] . '/shared_servers';
  6445. try {
  6446. $headers = array(
  6447. "Accept" => "application/json",
  6448. "X-Plex-Token" => $this->config['plexToken']
  6449. );
  6450. $response = Requests::get($url, $headers, array());
  6451. libxml_use_internal_errors(true);
  6452. if ($response->success) {
  6453. $libraryList = array();
  6454. $plex = simplexml_load_string($response->body);
  6455. foreach ($plex->SharedServer as $child) {
  6456. if (!empty($child['username'])) {
  6457. $username = (string)strtolower($child['username']);
  6458. $email = (string)strtolower($child['email']);
  6459. $libraryList['users'][$username] = (string)$child['id'];
  6460. $libraryList['emails'][$email] = (string)$child['id'];
  6461. $libraryList['both'][$username] = $email;
  6462. }
  6463. }
  6464. $libraryList = array_change_key_case($libraryList, CASE_LOWER);
  6465. return $libraryList;
  6466. }
  6467. } catch (Requests_Exception $e) {
  6468. $this->writeLog('error', 'Plex Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
  6469. }
  6470. }
  6471. break;
  6472. default:
  6473. # code...
  6474. break;
  6475. }
  6476. return false;
  6477. }
  6478. public function encrypt($password, $key = null)
  6479. {
  6480. $key = ($key) ? $key : ((isset($this->config['organizrHash'])) ? $this->config['organizrHash'] : null);
  6481. return openssl_encrypt($password, 'AES-256-CBC', $key, 0, $this->fillString($key, 16));
  6482. }
  6483. public function decrypt($password, $key = null)
  6484. {
  6485. if (empty($password)) {
  6486. return '';
  6487. }
  6488. $key = ($key) ? $key : ((isset($this->config['organizrHash'])) ? $this->config['organizrHash'] : null);
  6489. return openssl_decrypt($password, 'AES-256-CBC', $key, 0, $this->fillString($key, 16));
  6490. }
  6491. public function checkValidCert($file)
  6492. {
  6493. if (file_exists($file)) {
  6494. return filesize($file) > 0;
  6495. } else {
  6496. return false;
  6497. }
  6498. }
  6499. public function getCert()
  6500. {
  6501. $url = 'http://curl.haxx.se/ca/cacert.pem';
  6502. $file = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert.pem';
  6503. $file2 = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert-initial.pem';
  6504. $useCert = ($this->checkValidCert($file)) ? $file : $file2;
  6505. if ($this->config['selfSignedCert'] !== '') {
  6506. if (file_exists($this->config['selfSignedCert'])) {
  6507. return $this->config['selfSignedCert'];
  6508. }
  6509. }
  6510. $context = stream_context_create(
  6511. array(
  6512. 'ssl' => array(
  6513. 'verify_peer' => true,
  6514. 'cafile' => $useCert
  6515. )
  6516. )
  6517. );
  6518. if (!$this->checkValidCert($file) || (file_exists($file) && time() - 2592000 > filemtime($file))) {
  6519. file_put_contents($file, fopen($url, 'r', false, $context));
  6520. }
  6521. return ($this->checkValidCert($file)) ? $file : $file2;
  6522. }
  6523. public function hasCustomCert()
  6524. {
  6525. return file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem');
  6526. }
  6527. public function getCustomCert()
  6528. {
  6529. return ($this->hasCustomCert()) ? dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem' : false;
  6530. }
  6531. public function uploadCert()
  6532. {
  6533. $filesCheck = array_filter($_FILES);
  6534. if (!empty($filesCheck) && $this->approvedFileExtension($_FILES['file']['name'], 'cert')) {
  6535. ini_set('upload_max_filesize', '10M');
  6536. ini_set('post_max_size', '10M');
  6537. $tempFile = $_FILES['file']['tmp_name'];
  6538. $targetPath = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR;
  6539. $targetFile = $targetPath . 'custom.pem';
  6540. $this->setAPIResponse(null, pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' has been uploaded', null);
  6541. return move_uploaded_file($tempFile, $targetFile);
  6542. } else {
  6543. $this->setAPIResponse('error', pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' is not approved to be uploaded', 403);
  6544. return false;
  6545. }
  6546. }
  6547. public function plexJoinAPI($array)
  6548. {
  6549. $username = ($array['username']) ?? null;
  6550. $email = ($array['email']) ?? null;
  6551. $password = ($array['password']) ?? null;
  6552. if (!$username) {
  6553. $this->setAPIResponse('error', 'Username not supplied', 409);
  6554. return false;
  6555. }
  6556. if (!$email) {
  6557. $this->setAPIResponse('error', 'Email not supplied', 409);
  6558. return false;
  6559. }
  6560. if (!$password) {
  6561. $this->setAPIResponse('error', 'Password not supplied', 409);
  6562. return false;
  6563. }
  6564. return $this->plexJoin($username, $email, $password);
  6565. }
  6566. public function plexJoin($username, $email, $password)
  6567. {
  6568. try {
  6569. $url = 'https://plex.tv/api/v2/users';
  6570. $headers = array(
  6571. 'Accept' => 'application/json',
  6572. 'Content-Type' => 'application/x-www-form-urlencoded',
  6573. 'X-Plex-Product' => 'Organizr',
  6574. 'X-Plex-Version' => '2.0',
  6575. 'X-Plex-Client-Identifier' => $this->config['uuid'],
  6576. );
  6577. $data = array(
  6578. 'email' => $email,
  6579. 'username' => $username,
  6580. 'password' => $password,
  6581. );
  6582. $response = Requests::post($url, $headers, $data, array());
  6583. $json = json_decode($response->body, true);
  6584. $errors = !empty($json['errors']);
  6585. $success = empty($json['errors']);
  6586. //Use This for later
  6587. $errorMessage = '';
  6588. if ($errors) {
  6589. foreach ($json['errors'] as $error) {
  6590. if (isset($error['message']) && isset($error['field'])) {
  6591. $errorMessage .= "[Plex.tv Error: " . $error['message'] . " for field: (" . $error['field'] . ")]";
  6592. }
  6593. }
  6594. }
  6595. $msg = (!empty($success) && empty($errors)) ? 'User has joined Plex' : $errorMessage;
  6596. $status = (!empty($success) && empty($errors)) ? 'success' : 'error';
  6597. $code = (!empty($success) && empty($errors)) ? 200 : 422;
  6598. $this->setAPIResponse($status, $msg, $code);
  6599. return (!empty($success) && empty($errors));
  6600. } catch (Requests_Exception $e) {
  6601. $this->writeLog('error', 'Plex.TV Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
  6602. $this->setAPIResponse('error', 'An Error Occurred', 409);
  6603. return false;
  6604. }
  6605. return false;
  6606. }
  6607. public function lockCurrentUser()
  6608. {
  6609. if ($this->user['userID'] == '999') {
  6610. $this->setAPIResponse('error', 'Locking not allowed on Guest users', 409);
  6611. return false;
  6612. }
  6613. return $this->lockUser($this->user['userID']);
  6614. }
  6615. public function lockUser($id)
  6616. {
  6617. $user = $this->getUserById($id);
  6618. if (!$user) {
  6619. $this->setAPIResponse('error', 'User not found', 404);
  6620. return false;
  6621. }
  6622. $response = [
  6623. array(
  6624. 'function' => 'query',
  6625. 'query' => array(
  6626. 'UPDATE users SET',
  6627. ['locked' => '1'],
  6628. 'WHERE id = ?',
  6629. $id
  6630. )
  6631. ),
  6632. ];
  6633. $this->writeLog('success', 'User Lockout Function - User: ' . $user['username'] . ' account locked', $this->user['username']);
  6634. $this->setAPIResponse('success', 'User account locked', 200);
  6635. return $this->processQueries($response);
  6636. }
  6637. public function unlockCurrentUser($array)
  6638. {
  6639. if ($array['password'] == '') {
  6640. $this->setAPIResponse('error', 'Password Not Set', 422);
  6641. return false;
  6642. }
  6643. $user = $this->getUserById($this->user['userID']);
  6644. if (!password_verify($array['password'], $user['password'])) {
  6645. $this->setAPIResponse('error', 'Password Incorrect', 401);
  6646. return false;
  6647. }
  6648. return $this->unlockUser($this->user['userID']);
  6649. }
  6650. public function unlockUser($id)
  6651. {
  6652. $user = $this->getUserById($id);
  6653. if (!$user) {
  6654. $this->setAPIResponse('error', 'User not found', 404);
  6655. return false;
  6656. }
  6657. $response = [
  6658. array(
  6659. 'function' => 'query',
  6660. 'query' => array(
  6661. 'UPDATE users SET',
  6662. ['locked' => '0'],
  6663. 'WHERE id = ?',
  6664. $id
  6665. )
  6666. ),
  6667. ];
  6668. $this->writeLog('success', 'User Lockout Function - User: ' . $user['username'] . ' account unlocked', $this->user['username']);
  6669. $this->setAPIResponse('success', 'User account unlocked', 200);
  6670. return $this->processQueries($response);
  6671. }
  6672. public function youtubeSearch($query)
  6673. {
  6674. if (!$query) {
  6675. $this->setAPIResponse('error', 'No query supplied', 422);
  6676. return false;
  6677. }
  6678. $keys = array(
  6679. 'AIzaSyBsdt8nLJRMTwOq5PY5A5GLZ2q7scgn01w',
  6680. 'AIzaSyD-8SHutB60GCcSM8q_Fle38rJUV7ujd8k',
  6681. 'AIzaSyBzOpVBT6VII-b-8gWD0MOEosGg4hyhCsQ',
  6682. 'AIzaSyBKnRe1P8fpfBHgooJpmT0WOsrdUtZ4cpk'
  6683. );
  6684. $randomKeyIndex = array_rand($keys);
  6685. $key = $keys[$randomKeyIndex];
  6686. $apikey = ($this->config['youtubeAPI'] !== '') ? $this->config['youtubeAPI'] : $key;
  6687. $results = false;
  6688. $url = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=$query+official+trailer&part=snippet&maxResults=1&type=video&videoDuration=short&key=$apikey";
  6689. $response = Requests::get($url);
  6690. if ($response->success) {
  6691. $results = json_decode($response->body, true);
  6692. $this->setAPIResponse('success', null, 200, $results);
  6693. return $results;
  6694. } else {
  6695. $this->setAPIResponse('error', 'Bad response from YouTube', 500);
  6696. return false;
  6697. }
  6698. }
  6699. public function scrapePage($array)
  6700. {
  6701. try {
  6702. $url = $array['url'] ?? false;
  6703. $type = $array['type'] ?? false;
  6704. if (!$url) {
  6705. $this->setAPIResponse('error', 'URL was not supplied', 422);
  6706. return false;
  6707. }
  6708. $url = $this->qualifyURL($url);
  6709. $data = array(
  6710. 'full_url' => $url,
  6711. 'drill_url' => $this->qualifyURL($url, true)
  6712. );
  6713. $options = array('verify' => false);
  6714. $response = Requests::get($url, array(), $options);
  6715. $data['response_code'] = $response->status_code;
  6716. if ($response->success) {
  6717. $data['result'] = 'Success';
  6718. switch ($type) {
  6719. case 'html':
  6720. $data['data'] = html_entity_decode($response->body);
  6721. break;
  6722. case 'json':
  6723. $data['data'] = json_decode($response->body);
  6724. break;
  6725. default:
  6726. $data['data'] = $response->body;
  6727. }
  6728. $this->setAPIResponse('success', null, 200, $data);
  6729. return $data;
  6730. } else {
  6731. $this->setAPIResponse('error', 'Error getting successful response', 500);
  6732. return false;
  6733. }
  6734. } catch (Requests_Exception $e) {
  6735. $this->setAPIResponse('error', $e->getMessage(), 500);
  6736. return false;
  6737. }
  6738. }
  6739. public function chooseInstance($url = null, $token = null, $instance = 0, $type = null)
  6740. {
  6741. if (!$url || !$token) {
  6742. return false;
  6743. }
  6744. $list = $this->csvHomepageUrlToken($url, $token);
  6745. if ($type) {
  6746. $type = strtolower($type);
  6747. switch ($type) {
  6748. case 'url':
  6749. case 'token':
  6750. break;
  6751. default:
  6752. $type = 'url';
  6753. break;
  6754. }
  6755. if (is_numeric($instance)) {
  6756. return $list[$instance][$type];
  6757. } else {
  6758. return $list;
  6759. }
  6760. }
  6761. if (is_numeric($instance)) {
  6762. return $list[$instance];
  6763. } else {
  6764. return $list;
  6765. }
  6766. }
  6767. public function CBPFWTabs()
  6768. {
  6769. return '
  6770. <script>
  6771. /**
  6772. * cbpFWTabs.js v1.0.0
  6773. * http://www.codrops.com
  6774. *
  6775. * Licensed under the MIT license.
  6776. * http://www.opensource.org/licenses/mit-license.php
  6777. *
  6778. * Copyright 2014, Codrops
  6779. * http://www.codrops.com
  6780. */
  6781. ;( function( window ) {
  6782. \'use strict\';
  6783. function extend( a, b ) {
  6784. for( var key in b ) {
  6785. if( b.hasOwnProperty( key ) ) {
  6786. a[key] = b[key];
  6787. }
  6788. }
  6789. return a;
  6790. }
  6791. function CBPFWTabs( el, options ) {
  6792. this.el = el;
  6793. this.options = extend( {}, this.options );
  6794. extend( this.options, options );
  6795. this._init();
  6796. }
  6797. CBPFWTabs.prototype.options = {
  6798. start : 0
  6799. };
  6800. CBPFWTabs.prototype._init = function() {
  6801. // tabs elems
  6802. this.tabs = [].slice.call( this.el.querySelectorAll( \'nav > ul > li\' ) );
  6803. // content items
  6804. this.items = [].slice.call( this.el.querySelectorAll( \'.content-wrap > section\' ) );
  6805. // current index
  6806. this.current = -1;
  6807. // show current content item
  6808. try{
  6809. if(this.tabs[0].innerHTML.indexOf(\'#settings\') >= 0){
  6810. this._show(' . $this->config['defaultSettingsTab'] . ');
  6811. let tabId = $(this.items[' . $this->config['defaultSettingsTab'] . ']).attr("id") + "-anchor";
  6812. $("#" + tabId).click();
  6813. $("#" + tabId + " a").click();
  6814. }else{
  6815. this._show();
  6816. }
  6817. }catch{
  6818. this._show();
  6819. }
  6820. // init events
  6821. this._initEvents();
  6822. };
  6823. CBPFWTabs.prototype._initEvents = function() {
  6824. var self = this;
  6825. this.tabs.forEach( function( tab, idx ) {
  6826. tab.addEventListener( \'click\', function( ev ) {
  6827. ev.preventDefault();
  6828. self._show( idx );
  6829. } );
  6830. } );
  6831. };
  6832. CBPFWTabs.prototype._show = function( idx ) {
  6833. if( this.current >= 0 ) {
  6834. this.tabs[ this.current ].className = this.items[ this.current ].className = \'\';
  6835. }
  6836. // change current
  6837. this.current = idx != undefined ? idx : this.options.start >= 0 && this.options.start < this.items.length ? this.options.start : 0;
  6838. this.tabs[ this.current ].className = \'tab-current\';
  6839. this.items[ this.current ].className = \'content-current\';
  6840. };
  6841. // add to global namespace
  6842. window.CBPFWTabs = CBPFWTabs;
  6843. })( window );
  6844. </script>
  6845. ';
  6846. }
  6847. public function socksHeadingHTML($app)
  6848. {
  6849. return '
  6850. <h3 lang="en">' . ucwords($app) . ' SOCKS API Connection</h3>
  6851. <p>Using this feature allows you to access the API without having to reverse proxy it. Just access it from: </p>
  6852. <code class="elip hidden-xs">' . $this->getServerPath() . 'api/v2/socks/' . $app . '/</code>
  6853. <p>If you are using multiple URL\'s (using the csv method) you will have to use the url like these: </p>
  6854. <code class="elip hidden-xs">' . $this->getServerPath() . 'api/v2/multiple/socks/' . $app . '/1</code>
  6855. <br/>
  6856. <code class="elip hidden-xs">' . $this->getServerPath() . 'api/v2/multiple/socks/' . $app . '/2</code>
  6857. ';
  6858. }
  6859. public function socksListing($app = null)
  6860. {
  6861. switch ($app) {
  6862. case 'sonarr':
  6863. $appDetails = [
  6864. 'url' => 'sonarrURL',
  6865. 'enabled' => 'sonarrSocksEnabled',
  6866. 'auth' => 'sonarrSocksAuth',
  6867. 'header' => 'X-Api-Key'
  6868. ];
  6869. break;
  6870. case 'radarr':
  6871. $appDetails = [
  6872. 'url' => 'radarrURL',
  6873. 'enabled' => 'radarrSocksEnabled',
  6874. 'auth' => 'radarrSocksAuth',
  6875. 'header' => 'X-Api-Key'
  6876. ];
  6877. break;
  6878. case 'lidarr':
  6879. $appDetails = [
  6880. 'url' => 'lidarrURL',
  6881. 'enabled' => 'lidarrSocksEnabled',
  6882. 'auth' => 'lidarrSocksAuth',
  6883. 'header' => 'X-Api-Key'
  6884. ];
  6885. break;
  6886. case 'sabnzbd':
  6887. $appDetails = [
  6888. 'url' => 'sabnzbdURL',
  6889. 'enabled' => 'sabnzbdSocksEnabled',
  6890. 'auth' => 'sabnzbdSocksAuth',
  6891. 'header' => null
  6892. ];
  6893. break;
  6894. case 'nzbget':
  6895. $appDetails = [
  6896. 'url' => 'nzbgetURL',
  6897. 'enabled' => 'nzbgetSocksEnabled',
  6898. 'auth' => 'nzbgetSocksAuth',
  6899. 'header' => 'Authorization'
  6900. ];
  6901. break;
  6902. case 'tautulli':
  6903. $appDetails = [
  6904. 'url' => 'tautulliURL',
  6905. 'enabled' => 'tautulliSocksEnabled',
  6906. 'auth' => 'tautulliSocksAuth',
  6907. 'header' => null
  6908. ];
  6909. break;
  6910. case 'qbittorrent':
  6911. $appDetails = [
  6912. 'url' => 'qBittorrentURL',
  6913. 'enabled' => 'qBittorrentSocksEnabled',
  6914. 'auth' => 'qBittorrentSocksAuth',
  6915. 'header' => null
  6916. ];
  6917. break;
  6918. default:
  6919. $appDetails = null;
  6920. }
  6921. return $appDetails;
  6922. }
  6923. public function socks($appDetails, $requestObject, $multiple = null)
  6924. {
  6925. $url = $appDetails['url'];
  6926. $enabled = $appDetails['enabled'];
  6927. $auth = $appDetails['auth'];
  6928. $header = $appDetails['header'];
  6929. $error = false;
  6930. if (!$this->config[$enabled]) {
  6931. $error = true;
  6932. $this->setAPIResponse('error', 'SOCKS module is not enabled', 409);
  6933. }
  6934. if (!$this->qualifyRequest($this->config[$auth], true)) {
  6935. $error = true;
  6936. }
  6937. if (strpos($this->config[$url], ',') !== false) {
  6938. if (!$multiple) {
  6939. $error = true;
  6940. $this->setAPIResponse('error', 'Multiple URLs found in field, please use /api/v2/multiple/socks endpoint', 409);
  6941. }
  6942. } else {
  6943. if ($multiple) {
  6944. $error = true;
  6945. $this->setAPIResponse('error', 'Multiple endpoint accessed but multiple URLs not found in field, please use /api/v2/socks endpoint', 409);
  6946. }
  6947. }
  6948. if (!$error) {
  6949. if ($multiple) {
  6950. $instance = $multiple - 1;
  6951. $pre = explode('/api/v2/multiple/socks/', $requestObject->getUri()->getPath());
  6952. $pre[1] = $this->replace_first('/' . $multiple . '/', '/', $pre[1]);
  6953. // sent url twice since we arent using tokens
  6954. $list = $this->csvHomepageUrlToken($this->config[$url], $this->config[$url]);
  6955. $appURL = $list[$instance]['url'];
  6956. } else {
  6957. $pre = explode('/api/v2/socks/', $requestObject->getUri()->getPath());
  6958. $appURL = $this->config[$url];
  6959. }
  6960. $endpoint = explode('/', $pre[1]);
  6961. $new = urldecode(preg_replace('/' . $endpoint[0] . '/', '', $pre[1], 1));
  6962. $getParams = ($_GET) ? '?' . http_build_query($_GET) : '';
  6963. $url = $this->qualifyURL($appURL) . $new . $getParams;
  6964. $url = $this->cleanPath($url);
  6965. $options = ($this->localURL($appURL)) ? array('verify' => false, 'timeout' => 120) : array('timeout' => 120);
  6966. $headers = [];
  6967. $apiData = $this->json_validator($this->apiData($requestObject)) ? json_encode($this->apiData($requestObject)) : $this->apiData($requestObject);
  6968. if ($header) {
  6969. if ($requestObject->hasHeader($header)) {
  6970. $headerKey = $requestObject->getHeaderLine($header);
  6971. $headers[$header] = $headerKey;
  6972. }
  6973. }
  6974. $debugInformation = [
  6975. 'type' => $requestObject->getMethod(),
  6976. 'headerType' => $requestObject->getHeaderLine('Content-Type'),
  6977. 'header' => $header,
  6978. 'headers' => $headers,
  6979. 'url' => $url,
  6980. 'options' => $options,
  6981. 'data' => $apiData
  6982. ];
  6983. //$this->debug(json_encode($debugInformation));
  6984. try {
  6985. switch ($requestObject->getMethod()) {
  6986. case 'GET':
  6987. $call = Requests::get($url, $headers, $options);
  6988. break;
  6989. case 'POST':
  6990. $call = Requests::post($url, $headers, $apiData, $options);
  6991. break;
  6992. case 'DELETE':
  6993. $call = Requests::delete($url, $headers, $options);
  6994. break;
  6995. case 'PUT':
  6996. $call = Requests::put($url, $headers, $apiData, $options);
  6997. break;
  6998. default:
  6999. $call = Requests::get($url, $headers, $options);
  7000. }
  7001. return $call->body;
  7002. } catch (Requests_Exception $e) {
  7003. $this->setAPIResponse('error', $e->getMessage(), 500);
  7004. return null;
  7005. }
  7006. } else {
  7007. return null;
  7008. }
  7009. }
  7010. public function getPlexServers()
  7011. {
  7012. if ($this->config['plexToken'] == '') {
  7013. $this->setAPIResponse('error', 'Plex Token cannot be empty', 422);
  7014. return false;
  7015. }
  7016. $ownedOnly = isset($_GET['owned']) ?? false;
  7017. $url = $this->qualifyURL('https://plex.tv/pms/servers');
  7018. $options = ($this->localURL($url)) ? array('verify' => false) : array();
  7019. $headers = [
  7020. 'X-Plex-Product' => 'Organizr',
  7021. 'X-Plex-Version' => '2.0',
  7022. 'X-Plex-Client-Identifier' => '01010101-10101010',
  7023. 'X-Plex-Token' => $this->config['plexToken'],
  7024. ];
  7025. try {
  7026. $response = Requests::get($url, $headers, $options);
  7027. libxml_use_internal_errors(true);
  7028. if ($response->success) {
  7029. $items = array();
  7030. $plex = simplexml_load_string($response->body);
  7031. foreach ($plex as $server) {
  7032. if ($ownedOnly) {
  7033. if ($server['owned'] == 1) {
  7034. $items[] = array(
  7035. 'name' => (string)$server['name'],
  7036. 'address' => (string)$server['address'],
  7037. 'machineIdentifier' => (string)$server['machineIdentifier'],
  7038. 'owned' => (float)$server['owned'],
  7039. );
  7040. }
  7041. } else {
  7042. $items[] = array(
  7043. 'name' => (string)$server['name'],
  7044. 'address' => (string)$server['address'],
  7045. 'machineIdentifier' => (string)$server['machineIdentifier'],
  7046. 'owned' => (float)$server['owned'],
  7047. );
  7048. }
  7049. }
  7050. $this->setAPIResponse('success', null, 200, $items);
  7051. return $items;
  7052. }
  7053. } catch (Requests_Exception $e) {
  7054. $this->writeLog('success', 'Plex Get Servers Function - Error: ' . $e->getMessage(), 'SYSTEM');
  7055. $this->setAPIResponse('error', $e->getMessage(), 500);
  7056. }
  7057. }
  7058. public function getIcons()
  7059. {
  7060. $term = $_GET['search'] ?? null;
  7061. $page = $_GET['page'] ?? 1;
  7062. $limit = $_GET['limit'] ?? 20;
  7063. $offset = ($page * $limit) - $limit;
  7064. $goodIcons['results'] = [];
  7065. $goodIcons['limit'] = $limit;
  7066. $goodIcons['page'] = $page;
  7067. $goodIcons['term'] = $term;
  7068. $allIcons = file_get_contents($this->root . '/js/icons.json');
  7069. $iconListing = json_decode($allIcons, true);
  7070. foreach ($iconListing as $setKey => $set) {
  7071. foreach ($set['children'] as $k => $v) {
  7072. if (stripos($v['text'], $term) !== false || !$term) {
  7073. $goodIcons['results'][] = $v;
  7074. }
  7075. }
  7076. }
  7077. $total = count($goodIcons['results']);
  7078. $goodIcons['total'] = $total;
  7079. $goodIcons['results'] = array_slice($goodIcons['results'], $offset, $limit);
  7080. $goodIcons['pagination']['more'] = $page < (ceil($total / $limit));
  7081. return $goodIcons;
  7082. }
  7083. public function getJournalMode()
  7084. {
  7085. $response = [
  7086. array(
  7087. 'function' => 'fetch',
  7088. 'query' => 'PRAGMA journal_mode',
  7089. ),
  7090. ];
  7091. $query = $this->processQueries($response);
  7092. if ($query) {
  7093. if ($query['journal_mode']) {
  7094. $this->setResponse(200, null, $query);
  7095. } else {
  7096. $this->setResponse(500, 'Error getting Journal Mode');
  7097. }
  7098. } else {
  7099. $this->setResponse(404, 'Journal Mode not found');
  7100. }
  7101. return $query;
  7102. }
  7103. public function setJournalMode($option = 'WAL')
  7104. {
  7105. $option = strtoupper($option);
  7106. switch ($option) {
  7107. case 'WAL':
  7108. case 'DELETE':
  7109. break;
  7110. default:
  7111. return false;
  7112. }
  7113. $response = [
  7114. array(
  7115. 'function' => 'fetch',
  7116. 'query' => 'PRAGMA journal_mode = \'' . $option . '\';',
  7117. ),
  7118. ];
  7119. $query = $this->processQueries($response);
  7120. if ($query) {
  7121. if ($query['journal_mode']) {
  7122. $this->setResponse(200, 'Journal Mode updated to: ' . $option, $query);
  7123. } else {
  7124. $this->setResponse(500, 'Error getting Journal Mode');
  7125. }
  7126. } else {
  7127. $this->setResponse(404, 'Journal Mode not found');
  7128. }
  7129. return $query;
  7130. }
  7131. protected function processQueries(array $request, $migration = false)
  7132. {
  7133. $results = array();
  7134. $firstKey = '';
  7135. try {
  7136. foreach ($request as $k => $v) {
  7137. $query = ($migration) ? $this->otherDb->query($v['query']) : $this->db->query($v['query']);
  7138. $keyName = (isset($v['key'])) ? $v['key'] : $k;
  7139. $firstKey = (isset($v['key']) && $k == 0) ? $v['key'] : $k;
  7140. switch ($v['function']) {
  7141. case 'fetchAll':
  7142. $results[$keyName] = $query->fetchAll();
  7143. break;
  7144. case 'fetch':
  7145. // PHP 8 Fix?
  7146. $query->setRowClass(null);
  7147. $results[$keyName] = $query->fetch();
  7148. break;
  7149. case 'getAffectedRows':
  7150. $results[$keyName] = $query->getAffectedRows();
  7151. break;
  7152. case 'getRowCount':
  7153. $results[$keyName] = $query->getRowCount();
  7154. break;
  7155. case 'fetchSingle':
  7156. // PHP 8 Fix?
  7157. $query->setRowClass(null);
  7158. $results[$keyName] = $query->fetchSingle();
  7159. break;
  7160. case 'query':
  7161. $results[$keyName] = $query;
  7162. break;
  7163. default:
  7164. return false;
  7165. }
  7166. }
  7167. } catch (Exception $e) {
  7168. $this->debug($e->getMessage());
  7169. return false;
  7170. }
  7171. return count($request) > 1 ? $results : $results[$firstKey];
  7172. }
  7173. }