class-wp-xmlrpc-server.php 197 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573
  1. <?php
  2. /**
  3. * XML-RPC protocol support for WordPress
  4. *
  5. * @package WordPress
  6. * @subpackage Publishing
  7. */
  8. /**
  9. * WordPress XMLRPC server implementation.
  10. *
  11. * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and
  12. * pingback. Additional WordPress API for managing comments, pages, posts,
  13. * options, etc.
  14. *
  15. * As of WordPress 3.5.0, XML-RPC is enabled by default. It can be disabled
  16. * via the {@see 'xmlrpc_enabled'} filter found in wp_xmlrpc_server::login().
  17. *
  18. * @since 1.5.0
  19. *
  20. * @see IXR_Server
  21. */
  22. class wp_xmlrpc_server extends IXR_Server {
  23. /**
  24. * Methods.
  25. *
  26. * @var array
  27. */
  28. public $methods;
  29. /**
  30. * Blog options.
  31. *
  32. * @var array
  33. */
  34. public $blog_options;
  35. /**
  36. * IXR_Error instance.
  37. *
  38. * @var IXR_Error
  39. */
  40. public $error;
  41. /**
  42. * Flags that the user authentication has failed in this instance of wp_xmlrpc_server.
  43. *
  44. * @var bool
  45. */
  46. protected $auth_failed = false;
  47. /**
  48. * Registers all of the XMLRPC methods that XMLRPC server understands.
  49. *
  50. * Sets up server and method property. Passes XMLRPC
  51. * methods through the {@see 'xmlrpc_methods'} filter to allow plugins to extend
  52. * or replace XML-RPC methods.
  53. *
  54. * @since 1.5.0
  55. */
  56. public function __construct() {
  57. $this->methods = array(
  58. // WordPress API
  59. 'wp.getUsersBlogs' => 'this:wp_getUsersBlogs',
  60. 'wp.newPost' => 'this:wp_newPost',
  61. 'wp.editPost' => 'this:wp_editPost',
  62. 'wp.deletePost' => 'this:wp_deletePost',
  63. 'wp.getPost' => 'this:wp_getPost',
  64. 'wp.getPosts' => 'this:wp_getPosts',
  65. 'wp.newTerm' => 'this:wp_newTerm',
  66. 'wp.editTerm' => 'this:wp_editTerm',
  67. 'wp.deleteTerm' => 'this:wp_deleteTerm',
  68. 'wp.getTerm' => 'this:wp_getTerm',
  69. 'wp.getTerms' => 'this:wp_getTerms',
  70. 'wp.getTaxonomy' => 'this:wp_getTaxonomy',
  71. 'wp.getTaxonomies' => 'this:wp_getTaxonomies',
  72. 'wp.getUser' => 'this:wp_getUser',
  73. 'wp.getUsers' => 'this:wp_getUsers',
  74. 'wp.getProfile' => 'this:wp_getProfile',
  75. 'wp.editProfile' => 'this:wp_editProfile',
  76. 'wp.getPage' => 'this:wp_getPage',
  77. 'wp.getPages' => 'this:wp_getPages',
  78. 'wp.newPage' => 'this:wp_newPage',
  79. 'wp.deletePage' => 'this:wp_deletePage',
  80. 'wp.editPage' => 'this:wp_editPage',
  81. 'wp.getPageList' => 'this:wp_getPageList',
  82. 'wp.getAuthors' => 'this:wp_getAuthors',
  83. 'wp.getCategories' => 'this:mw_getCategories', // Alias
  84. 'wp.getTags' => 'this:wp_getTags',
  85. 'wp.newCategory' => 'this:wp_newCategory',
  86. 'wp.deleteCategory' => 'this:wp_deleteCategory',
  87. 'wp.suggestCategories' => 'this:wp_suggestCategories',
  88. 'wp.uploadFile' => 'this:mw_newMediaObject', // Alias
  89. 'wp.deleteFile' => 'this:wp_deletePost', // Alias
  90. 'wp.getCommentCount' => 'this:wp_getCommentCount',
  91. 'wp.getPostStatusList' => 'this:wp_getPostStatusList',
  92. 'wp.getPageStatusList' => 'this:wp_getPageStatusList',
  93. 'wp.getPageTemplates' => 'this:wp_getPageTemplates',
  94. 'wp.getOptions' => 'this:wp_getOptions',
  95. 'wp.setOptions' => 'this:wp_setOptions',
  96. 'wp.getComment' => 'this:wp_getComment',
  97. 'wp.getComments' => 'this:wp_getComments',
  98. 'wp.deleteComment' => 'this:wp_deleteComment',
  99. 'wp.editComment' => 'this:wp_editComment',
  100. 'wp.newComment' => 'this:wp_newComment',
  101. 'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
  102. 'wp.getMediaItem' => 'this:wp_getMediaItem',
  103. 'wp.getMediaLibrary' => 'this:wp_getMediaLibrary',
  104. 'wp.getPostFormats' => 'this:wp_getPostFormats',
  105. 'wp.getPostType' => 'this:wp_getPostType',
  106. 'wp.getPostTypes' => 'this:wp_getPostTypes',
  107. 'wp.getRevisions' => 'this:wp_getRevisions',
  108. 'wp.restoreRevision' => 'this:wp_restoreRevision',
  109. // Blogger API
  110. 'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
  111. 'blogger.getUserInfo' => 'this:blogger_getUserInfo',
  112. 'blogger.getPost' => 'this:blogger_getPost',
  113. 'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
  114. 'blogger.newPost' => 'this:blogger_newPost',
  115. 'blogger.editPost' => 'this:blogger_editPost',
  116. 'blogger.deletePost' => 'this:blogger_deletePost',
  117. // MetaWeblog API (with MT extensions to structs)
  118. 'metaWeblog.newPost' => 'this:mw_newPost',
  119. 'metaWeblog.editPost' => 'this:mw_editPost',
  120. 'metaWeblog.getPost' => 'this:mw_getPost',
  121. 'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
  122. 'metaWeblog.getCategories' => 'this:mw_getCategories',
  123. 'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
  124. // MetaWeblog API aliases for Blogger API
  125. // see http://www.xmlrpc.com/stories/storyReader$2460
  126. 'metaWeblog.deletePost' => 'this:blogger_deletePost',
  127. 'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
  128. // MovableType API
  129. 'mt.getCategoryList' => 'this:mt_getCategoryList',
  130. 'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
  131. 'mt.getPostCategories' => 'this:mt_getPostCategories',
  132. 'mt.setPostCategories' => 'this:mt_setPostCategories',
  133. 'mt.supportedMethods' => 'this:mt_supportedMethods',
  134. 'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
  135. 'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
  136. 'mt.publishPost' => 'this:mt_publishPost',
  137. // PingBack
  138. 'pingback.ping' => 'this:pingback_ping',
  139. 'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
  140. 'demo.sayHello' => 'this:sayHello',
  141. 'demo.addTwoNumbers' => 'this:addTwoNumbers'
  142. );
  143. $this->initialise_blog_option_info();
  144. /**
  145. * Filters the methods exposed by the XML-RPC server.
  146. *
  147. * This filter can be used to add new methods, and remove built-in methods.
  148. *
  149. * @since 1.5.0
  150. *
  151. * @param array $methods An array of XML-RPC methods.
  152. */
  153. $this->methods = apply_filters( 'xmlrpc_methods', $this->methods );
  154. }
  155. /**
  156. * Make private/protected methods readable for backward compatibility.
  157. *
  158. * @since 4.0.0
  159. *
  160. * @param callable $name Method to call.
  161. * @param array $arguments Arguments to pass when calling.
  162. * @return array|IXR_Error|false Return value of the callback, false otherwise.
  163. */
  164. public function __call( $name, $arguments ) {
  165. if ( '_multisite_getUsersBlogs' === $name ) {
  166. return call_user_func_array( array( $this, $name ), $arguments );
  167. }
  168. return false;
  169. }
  170. /**
  171. * Serves the XML-RPC request.
  172. *
  173. * @since 2.9.0
  174. */
  175. public function serve_request() {
  176. $this->IXR_Server($this->methods);
  177. }
  178. /**
  179. * Test XMLRPC API by saying, "Hello!" to client.
  180. *
  181. * @since 1.5.0
  182. *
  183. * @return string Hello string response.
  184. */
  185. public function sayHello() {
  186. return 'Hello!';
  187. }
  188. /**
  189. * Test XMLRPC API by adding two numbers for client.
  190. *
  191. * @since 1.5.0
  192. *
  193. * @param array $args {
  194. * Method arguments. Note: arguments must be ordered as documented.
  195. *
  196. * @type int $number1 A number to add.
  197. * @type int $number2 A second number to add.
  198. * }
  199. * @return int Sum of the two given numbers.
  200. */
  201. public function addTwoNumbers( $args ) {
  202. $number1 = $args[0];
  203. $number2 = $args[1];
  204. return $number1 + $number2;
  205. }
  206. /**
  207. * Log user in.
  208. *
  209. * @since 2.8.0
  210. *
  211. * @param string $username User's username.
  212. * @param string $password User's password.
  213. * @return WP_User|bool WP_User object if authentication passed, false otherwise
  214. */
  215. public function login( $username, $password ) {
  216. /*
  217. * Respect old get_option() filters left for back-compat when the 'enable_xmlrpc'
  218. * option was deprecated in 3.5.0. Use the 'xmlrpc_enabled' hook instead.
  219. */
  220. $enabled = apply_filters( 'pre_option_enable_xmlrpc', false );
  221. if ( false === $enabled ) {
  222. $enabled = apply_filters( 'option_enable_xmlrpc', true );
  223. }
  224. /**
  225. * Filters whether XML-RPC methods requiring authentication are enabled.
  226. *
  227. * Contrary to the way it's named, this filter does not control whether XML-RPC is *fully*
  228. * enabled, rather, it only controls whether XML-RPC methods requiring authentication - such
  229. * as for publishing purposes - are enabled.
  230. *
  231. * Further, the filter does not control whether pingbacks or other custom endpoints that don't
  232. * require authentication are enabled. This behavior is expected, and due to how parity was matched
  233. * with the `enable_xmlrpc` UI option the filter replaced when it was introduced in 3.5.
  234. *
  235. * To disable XML-RPC methods that require authentication, use:
  236. *
  237. * add_filter( 'xmlrpc_enabled', '__return_false' );
  238. *
  239. * For more granular control over all XML-RPC methods and requests, see the {@see 'xmlrpc_methods'}
  240. * and {@see 'xmlrpc_element_limit'} hooks.
  241. *
  242. * @since 3.5.0
  243. *
  244. * @param bool $enabled Whether XML-RPC is enabled. Default true.
  245. */
  246. $enabled = apply_filters( 'xmlrpc_enabled', $enabled );
  247. if ( ! $enabled ) {
  248. $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
  249. return false;
  250. }
  251. if ( $this->auth_failed ) {
  252. $user = new WP_Error( 'login_prevented' );
  253. } else {
  254. $user = wp_authenticate( $username, $password );
  255. }
  256. if ( is_wp_error( $user ) ) {
  257. $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
  258. // Flag that authentication has failed once on this wp_xmlrpc_server instance
  259. $this->auth_failed = true;
  260. /**
  261. * Filters the XML-RPC user login error message.
  262. *
  263. * @since 3.5.0
  264. *
  265. * @param string $error The XML-RPC error message.
  266. * @param WP_User $user WP_User object.
  267. */
  268. $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
  269. return false;
  270. }
  271. wp_set_current_user( $user->ID );
  272. return $user;
  273. }
  274. /**
  275. * Check user's credentials. Deprecated.
  276. *
  277. * @since 1.5.0
  278. * @deprecated 2.8.0 Use wp_xmlrpc_server::login()
  279. * @see wp_xmlrpc_server::login()
  280. *
  281. * @param string $username User's username.
  282. * @param string $password User's password.
  283. * @return bool Whether authentication passed.
  284. */
  285. public function login_pass_ok( $username, $password ) {
  286. return (bool) $this->login( $username, $password );
  287. }
  288. /**
  289. * Escape string or array of strings for database.
  290. *
  291. * @since 1.5.2
  292. *
  293. * @param string|array $data Escape single string or array of strings.
  294. * @return string|void Returns with string is passed, alters by-reference
  295. * when array is passed.
  296. */
  297. public function escape( &$data ) {
  298. if ( ! is_array( $data ) )
  299. return wp_slash( $data );
  300. foreach ( $data as &$v ) {
  301. if ( is_array( $v ) )
  302. $this->escape( $v );
  303. elseif ( ! is_object( $v ) )
  304. $v = wp_slash( $v );
  305. }
  306. }
  307. /**
  308. * Retrieve custom fields for post.
  309. *
  310. * @since 2.5.0
  311. *
  312. * @param int $post_id Post ID.
  313. * @return array Custom fields, if exist.
  314. */
  315. public function get_custom_fields($post_id) {
  316. $post_id = (int) $post_id;
  317. $custom_fields = array();
  318. foreach ( (array) has_meta($post_id) as $meta ) {
  319. // Don't expose protected fields.
  320. if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
  321. continue;
  322. $custom_fields[] = array(
  323. "id" => $meta['meta_id'],
  324. "key" => $meta['meta_key'],
  325. "value" => $meta['meta_value']
  326. );
  327. }
  328. return $custom_fields;
  329. }
  330. /**
  331. * Set custom fields for post.
  332. *
  333. * @since 2.5.0
  334. *
  335. * @param int $post_id Post ID.
  336. * @param array $fields Custom fields.
  337. */
  338. public function set_custom_fields($post_id, $fields) {
  339. $post_id = (int) $post_id;
  340. foreach ( (array) $fields as $meta ) {
  341. if ( isset($meta['id']) ) {
  342. $meta['id'] = (int) $meta['id'];
  343. $pmeta = get_metadata_by_mid( 'post', $meta['id'] );
  344. if ( ! $pmeta || $pmeta->post_id != $post_id ) {
  345. continue;
  346. }
  347. if ( isset($meta['key']) ) {
  348. $meta['key'] = wp_unslash( $meta['key'] );
  349. if ( $meta['key'] !== $pmeta->meta_key )
  350. continue;
  351. $meta['value'] = wp_unslash( $meta['value'] );
  352. if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
  353. update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
  354. } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
  355. delete_metadata_by_mid( 'post', $meta['id'] );
  356. }
  357. } elseif ( current_user_can( 'add_post_meta', $post_id, wp_unslash( $meta['key'] ) ) ) {
  358. add_post_meta( $post_id, $meta['key'], $meta['value'] );
  359. }
  360. }
  361. }
  362. /**
  363. * Retrieve custom fields for a term.
  364. *
  365. * @since 4.9.0
  366. *
  367. * @param int $term_id Term ID.
  368. * @return array Array of custom fields, if they exist.
  369. */
  370. public function get_term_custom_fields( $term_id ) {
  371. $term_id = (int) $term_id;
  372. $custom_fields = array();
  373. foreach ( (array) has_term_meta( $term_id ) as $meta ) {
  374. if ( ! current_user_can( 'edit_term_meta', $term_id ) ) {
  375. continue;
  376. }
  377. $custom_fields[] = array(
  378. 'id' => $meta['meta_id'],
  379. 'key' => $meta['meta_key'],
  380. 'value' => $meta['meta_value'],
  381. );
  382. }
  383. return $custom_fields;
  384. }
  385. /**
  386. * Set custom fields for a term.
  387. *
  388. * @since 4.9.0
  389. *
  390. * @param int $term_id Term ID.
  391. * @param array $fields Custom fields.
  392. */
  393. public function set_term_custom_fields( $term_id, $fields ) {
  394. $term_id = (int) $term_id;
  395. foreach ( (array) $fields as $meta ) {
  396. if ( isset( $meta['id'] ) ) {
  397. $meta['id'] = (int) $meta['id'];
  398. $pmeta = get_metadata_by_mid( 'term', $meta['id'] );
  399. if ( isset( $meta['key'] ) ) {
  400. $meta['key'] = wp_unslash( $meta['key'] );
  401. if ( $meta['key'] !== $pmeta->meta_key ) {
  402. continue;
  403. }
  404. $meta['value'] = wp_unslash( $meta['value'] );
  405. if ( current_user_can( 'edit_term_meta', $term_id ) ) {
  406. update_metadata_by_mid( 'term', $meta['id'], $meta['value'] );
  407. }
  408. } elseif ( current_user_can( 'delete_term_meta', $term_id ) ) {
  409. delete_metadata_by_mid( 'term', $meta['id'] );
  410. }
  411. } elseif ( current_user_can( 'add_term_meta', $term_id ) ) {
  412. add_term_meta( $term_id, $meta['key'], $meta['value'] );
  413. }
  414. }
  415. }
  416. /**
  417. * Set up blog options property.
  418. *
  419. * Passes property through {@see 'xmlrpc_blog_options'} filter.
  420. *
  421. * @since 2.6.0
  422. */
  423. public function initialise_blog_option_info() {
  424. $this->blog_options = array(
  425. // Read only options
  426. 'software_name' => array(
  427. 'desc' => __( 'Software Name' ),
  428. 'readonly' => true,
  429. 'value' => 'WordPress'
  430. ),
  431. 'software_version' => array(
  432. 'desc' => __( 'Software Version' ),
  433. 'readonly' => true,
  434. 'value' => get_bloginfo( 'version' )
  435. ),
  436. 'blog_url' => array(
  437. 'desc' => __( 'WordPress Address (URL)' ),
  438. 'readonly' => true,
  439. 'option' => 'siteurl'
  440. ),
  441. 'home_url' => array(
  442. 'desc' => __( 'Site Address (URL)' ),
  443. 'readonly' => true,
  444. 'option' => 'home'
  445. ),
  446. 'login_url' => array(
  447. 'desc' => __( 'Login Address (URL)' ),
  448. 'readonly' => true,
  449. 'value' => wp_login_url( )
  450. ),
  451. 'admin_url' => array(
  452. 'desc' => __( 'The URL to the admin area' ),
  453. 'readonly' => true,
  454. 'value' => get_admin_url( )
  455. ),
  456. 'image_default_link_type' => array(
  457. 'desc' => __( 'Image default link type' ),
  458. 'readonly' => true,
  459. 'option' => 'image_default_link_type'
  460. ),
  461. 'image_default_size' => array(
  462. 'desc' => __( 'Image default size' ),
  463. 'readonly' => true,
  464. 'option' => 'image_default_size'
  465. ),
  466. 'image_default_align' => array(
  467. 'desc' => __( 'Image default align' ),
  468. 'readonly' => true,
  469. 'option' => 'image_default_align'
  470. ),
  471. 'template' => array(
  472. 'desc' => __( 'Template' ),
  473. 'readonly' => true,
  474. 'option' => 'template'
  475. ),
  476. 'stylesheet' => array(
  477. 'desc' => __( 'Stylesheet' ),
  478. 'readonly' => true,
  479. 'option' => 'stylesheet'
  480. ),
  481. 'post_thumbnail' => array(
  482. 'desc' => __('Post Thumbnail'),
  483. 'readonly' => true,
  484. 'value' => current_theme_supports( 'post-thumbnails' )
  485. ),
  486. // Updatable options
  487. 'time_zone' => array(
  488. 'desc' => __( 'Time Zone' ),
  489. 'readonly' => false,
  490. 'option' => 'gmt_offset'
  491. ),
  492. 'blog_title' => array(
  493. 'desc' => __( 'Site Title' ),
  494. 'readonly' => false,
  495. 'option' => 'blogname'
  496. ),
  497. 'blog_tagline' => array(
  498. 'desc' => __( 'Site Tagline' ),
  499. 'readonly' => false,
  500. 'option' => 'blogdescription'
  501. ),
  502. 'date_format' => array(
  503. 'desc' => __( 'Date Format' ),
  504. 'readonly' => false,
  505. 'option' => 'date_format'
  506. ),
  507. 'time_format' => array(
  508. 'desc' => __( 'Time Format' ),
  509. 'readonly' => false,
  510. 'option' => 'time_format'
  511. ),
  512. 'users_can_register' => array(
  513. 'desc' => __( 'Allow new users to sign up' ),
  514. 'readonly' => false,
  515. 'option' => 'users_can_register'
  516. ),
  517. 'thumbnail_size_w' => array(
  518. 'desc' => __( 'Thumbnail Width' ),
  519. 'readonly' => false,
  520. 'option' => 'thumbnail_size_w'
  521. ),
  522. 'thumbnail_size_h' => array(
  523. 'desc' => __( 'Thumbnail Height' ),
  524. 'readonly' => false,
  525. 'option' => 'thumbnail_size_h'
  526. ),
  527. 'thumbnail_crop' => array(
  528. 'desc' => __( 'Crop thumbnail to exact dimensions' ),
  529. 'readonly' => false,
  530. 'option' => 'thumbnail_crop'
  531. ),
  532. 'medium_size_w' => array(
  533. 'desc' => __( 'Medium size image width' ),
  534. 'readonly' => false,
  535. 'option' => 'medium_size_w'
  536. ),
  537. 'medium_size_h' => array(
  538. 'desc' => __( 'Medium size image height' ),
  539. 'readonly' => false,
  540. 'option' => 'medium_size_h'
  541. ),
  542. 'medium_large_size_w' => array(
  543. 'desc' => __( 'Medium-Large size image width' ),
  544. 'readonly' => false,
  545. 'option' => 'medium_large_size_w'
  546. ),
  547. 'medium_large_size_h' => array(
  548. 'desc' => __( 'Medium-Large size image height' ),
  549. 'readonly' => false,
  550. 'option' => 'medium_large_size_h'
  551. ),
  552. 'large_size_w' => array(
  553. 'desc' => __( 'Large size image width' ),
  554. 'readonly' => false,
  555. 'option' => 'large_size_w'
  556. ),
  557. 'large_size_h' => array(
  558. 'desc' => __( 'Large size image height' ),
  559. 'readonly' => false,
  560. 'option' => 'large_size_h'
  561. ),
  562. 'default_comment_status' => array(
  563. 'desc' => __( 'Allow people to post comments on new articles' ),
  564. 'readonly' => false,
  565. 'option' => 'default_comment_status'
  566. ),
  567. 'default_ping_status' => array(
  568. 'desc' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles' ),
  569. 'readonly' => false,
  570. 'option' => 'default_ping_status'
  571. )
  572. );
  573. /**
  574. * Filters the XML-RPC blog options property.
  575. *
  576. * @since 2.6.0
  577. *
  578. * @param array $blog_options An array of XML-RPC blog options.
  579. */
  580. $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
  581. }
  582. /**
  583. * Retrieve the blogs of the user.
  584. *
  585. * @since 2.6.0
  586. *
  587. * @param array $args {
  588. * Method arguments. Note: arguments must be ordered as documented.
  589. *
  590. * @type string $username Username.
  591. * @type string $password Password.
  592. * }
  593. * @return array|IXR_Error Array contains:
  594. * - 'isAdmin'
  595. * - 'isPrimary' - whether the blog is the user's primary blog
  596. * - 'url'
  597. * - 'blogid'
  598. * - 'blogName'
  599. * - 'xmlrpc' - url of xmlrpc endpoint
  600. */
  601. public function wp_getUsersBlogs( $args ) {
  602. if ( ! $this->minimum_args( $args, 2 ) ) {
  603. return $this->error;
  604. }
  605. // If this isn't on WPMU then just use blogger_getUsersBlogs
  606. if ( !is_multisite() ) {
  607. array_unshift( $args, 1 );
  608. return $this->blogger_getUsersBlogs( $args );
  609. }
  610. $this->escape( $args );
  611. $username = $args[0];
  612. $password = $args[1];
  613. if ( !$user = $this->login($username, $password) )
  614. return $this->error;
  615. /**
  616. * Fires after the XML-RPC user has been authenticated but before the rest of
  617. * the method logic begins.
  618. *
  619. * All built-in XML-RPC methods use the action xmlrpc_call, with a parameter
  620. * equal to the method's name, e.g., wp.getUsersBlogs, wp.newPost, etc.
  621. *
  622. * @since 2.5.0
  623. *
  624. * @param string $name The method name.
  625. */
  626. do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
  627. $blogs = (array) get_blogs_of_user( $user->ID );
  628. $struct = array();
  629. $primary_blog_id = 0;
  630. $active_blog = get_active_blog_for_user( $user->ID );
  631. if ( $active_blog ) {
  632. $primary_blog_id = (int) $active_blog->blog_id;
  633. }
  634. foreach ( $blogs as $blog ) {
  635. // Don't include blogs that aren't hosted at this site.
  636. if ( $blog->site_id != get_current_network_id() )
  637. continue;
  638. $blog_id = $blog->userblog_id;
  639. switch_to_blog( $blog_id );
  640. $is_admin = current_user_can( 'manage_options' );
  641. $is_primary = ( (int) $blog_id === $primary_blog_id );
  642. $struct[] = array(
  643. 'isAdmin' => $is_admin,
  644. 'isPrimary' => $is_primary,
  645. 'url' => home_url( '/' ),
  646. 'blogid' => (string) $blog_id,
  647. 'blogName' => get_option( 'blogname' ),
  648. 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ),
  649. );
  650. restore_current_blog();
  651. }
  652. return $struct;
  653. }
  654. /**
  655. * Checks if the method received at least the minimum number of arguments.
  656. *
  657. * @since 3.4.0
  658. *
  659. * @param string|array $args Sanitize single string or array of strings.
  660. * @param int $count Minimum number of arguments.
  661. * @return bool if `$args` contains at least $count arguments.
  662. */
  663. protected function minimum_args( $args, $count ) {
  664. if ( count( $args ) < $count ) {
  665. $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
  666. return false;
  667. }
  668. return true;
  669. }
  670. /**
  671. * Prepares taxonomy data for return in an XML-RPC object.
  672. *
  673. *
  674. * @param object $taxonomy The unprepared taxonomy data.
  675. * @param array $fields The subset of taxonomy fields to return.
  676. * @return array The prepared taxonomy data.
  677. */
  678. protected function _prepare_taxonomy( $taxonomy, $fields ) {
  679. $_taxonomy = array(
  680. 'name' => $taxonomy->name,
  681. 'label' => $taxonomy->label,
  682. 'hierarchical' => (bool) $taxonomy->hierarchical,
  683. 'public' => (bool) $taxonomy->public,
  684. 'show_ui' => (bool) $taxonomy->show_ui,
  685. '_builtin' => (bool) $taxonomy->_builtin,
  686. );
  687. if ( in_array( 'labels', $fields ) )
  688. $_taxonomy['labels'] = (array) $taxonomy->labels;
  689. if ( in_array( 'cap', $fields ) )
  690. $_taxonomy['cap'] = (array) $taxonomy->cap;
  691. if ( in_array( 'menu', $fields ) )
  692. $_taxonomy['show_in_menu'] = (bool) $_taxonomy->show_in_menu;
  693. if ( in_array( 'object_type', $fields ) )
  694. $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
  695. /**
  696. * Filters XML-RPC-prepared data for the given taxonomy.
  697. *
  698. * @since 3.4.0
  699. *
  700. * @param array $_taxonomy An array of taxonomy data.
  701. * @param WP_Taxonomy $taxonomy Taxonomy object.
  702. * @param array $fields The subset of taxonomy fields to return.
  703. */
  704. return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
  705. }
  706. /**
  707. * Prepares term data for return in an XML-RPC object.
  708. *
  709. *
  710. * @param array|object $term The unprepared term data.
  711. * @return array The prepared term data.
  712. */
  713. protected function _prepare_term( $term ) {
  714. $_term = $term;
  715. if ( ! is_array( $_term ) )
  716. $_term = get_object_vars( $_term );
  717. // For integers which may be larger than XML-RPC supports ensure we return strings.
  718. $_term['term_id'] = strval( $_term['term_id'] );
  719. $_term['term_group'] = strval( $_term['term_group'] );
  720. $_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
  721. $_term['parent'] = strval( $_term['parent'] );
  722. // Count we are happy to return as an integer because people really shouldn't use terms that much.
  723. $_term['count'] = intval( $_term['count'] );
  724. // Get term meta.
  725. $_term['custom_fields'] = $this->get_term_custom_fields( $_term['term_id'] );
  726. /**
  727. * Filters XML-RPC-prepared data for the given term.
  728. *
  729. * @since 3.4.0
  730. *
  731. * @param array $_term An array of term data.
  732. * @param array|object $term Term object or array.
  733. */
  734. return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
  735. }
  736. /**
  737. * Convert a WordPress date string to an IXR_Date object.
  738. *
  739. *
  740. * @param string $date Date string to convert.
  741. * @return IXR_Date IXR_Date object.
  742. */
  743. protected function _convert_date( $date ) {
  744. if ( $date === '0000-00-00 00:00:00' ) {
  745. return new IXR_Date( '00000000T00:00:00Z' );
  746. }
  747. return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
  748. }
  749. /**
  750. * Convert a WordPress GMT date string to an IXR_Date object.
  751. *
  752. *
  753. * @param string $date_gmt WordPress GMT date string.
  754. * @param string $date Date string.
  755. * @return IXR_Date IXR_Date object.
  756. */
  757. protected function _convert_date_gmt( $date_gmt, $date ) {
  758. if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
  759. return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
  760. }
  761. return $this->_convert_date( $date_gmt );
  762. }
  763. /**
  764. * Prepares post data for return in an XML-RPC object.
  765. *
  766. *
  767. * @param array $post The unprepared post data.
  768. * @param array $fields The subset of post type fields to return.
  769. * @return array The prepared post data.
  770. */
  771. protected function _prepare_post( $post, $fields ) {
  772. // Holds the data for this post. built up based on $fields.
  773. $_post = array( 'post_id' => strval( $post['ID'] ) );
  774. // Prepare common post fields.
  775. $post_fields = array(
  776. 'post_title' => $post['post_title'],
  777. 'post_date' => $this->_convert_date( $post['post_date'] ),
  778. 'post_date_gmt' => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
  779. 'post_modified' => $this->_convert_date( $post['post_modified'] ),
  780. 'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
  781. 'post_status' => $post['post_status'],
  782. 'post_type' => $post['post_type'],
  783. 'post_name' => $post['post_name'],
  784. 'post_author' => $post['post_author'],
  785. 'post_password' => $post['post_password'],
  786. 'post_excerpt' => $post['post_excerpt'],
  787. 'post_content' => $post['post_content'],
  788. 'post_parent' => strval( $post['post_parent'] ),
  789. 'post_mime_type' => $post['post_mime_type'],
  790. 'link' => get_permalink( $post['ID'] ),
  791. 'guid' => $post['guid'],
  792. 'menu_order' => intval( $post['menu_order'] ),
  793. 'comment_status' => $post['comment_status'],
  794. 'ping_status' => $post['ping_status'],
  795. 'sticky' => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
  796. );
  797. // Thumbnail.
  798. $post_fields['post_thumbnail'] = array();
  799. $thumbnail_id = get_post_thumbnail_id( $post['ID'] );
  800. if ( $thumbnail_id ) {
  801. $thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
  802. $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
  803. }
  804. // Consider future posts as published.
  805. if ( $post_fields['post_status'] === 'future' )
  806. $post_fields['post_status'] = 'publish';
  807. // Fill in blank post format.
  808. $post_fields['post_format'] = get_post_format( $post['ID'] );
  809. if ( empty( $post_fields['post_format'] ) )
  810. $post_fields['post_format'] = 'standard';
  811. // Merge requested $post_fields fields into $_post.
  812. if ( in_array( 'post', $fields ) ) {
  813. $_post = array_merge( $_post, $post_fields );
  814. } else {
  815. $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
  816. $_post = array_merge( $_post, $requested_fields );
  817. }
  818. $all_taxonomy_fields = in_array( 'taxonomies', $fields );
  819. if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
  820. $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
  821. $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
  822. $_post['terms'] = array();
  823. foreach ( $terms as $term ) {
  824. $_post['terms'][] = $this->_prepare_term( $term );
  825. }
  826. }
  827. if ( in_array( 'custom_fields', $fields ) )
  828. $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
  829. if ( in_array( 'enclosure', $fields ) ) {
  830. $_post['enclosure'] = array();
  831. $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
  832. if ( ! empty( $enclosures ) ) {
  833. $encdata = explode( "\n", $enclosures[0] );
  834. $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
  835. $_post['enclosure']['length'] = (int) trim( $encdata[1] );
  836. $_post['enclosure']['type'] = trim( $encdata[2] );
  837. }
  838. }
  839. /**
  840. * Filters XML-RPC-prepared date for the given post.
  841. *
  842. * @since 3.4.0
  843. *
  844. * @param array $_post An array of modified post data.
  845. * @param array $post An array of post data.
  846. * @param array $fields An array of post fields.
  847. */
  848. return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
  849. }
  850. /**
  851. * Prepares post data for return in an XML-RPC object.
  852. *
  853. * @since 3.4.0
  854. * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
  855. *
  856. * @param WP_Post_Type $post_type Post type object.
  857. * @param array $fields The subset of post fields to return.
  858. * @return array The prepared post type data.
  859. */
  860. protected function _prepare_post_type( $post_type, $fields ) {
  861. $_post_type = array(
  862. 'name' => $post_type->name,
  863. 'label' => $post_type->label,
  864. 'hierarchical' => (bool) $post_type->hierarchical,
  865. 'public' => (bool) $post_type->public,
  866. 'show_ui' => (bool) $post_type->show_ui,
  867. '_builtin' => (bool) $post_type->_builtin,
  868. 'has_archive' => (bool) $post_type->has_archive,
  869. 'supports' => get_all_post_type_supports( $post_type->name ),
  870. );
  871. if ( in_array( 'labels', $fields ) ) {
  872. $_post_type['labels'] = (array) $post_type->labels;
  873. }
  874. if ( in_array( 'cap', $fields ) ) {
  875. $_post_type['cap'] = (array) $post_type->cap;
  876. $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
  877. }
  878. if ( in_array( 'menu', $fields ) ) {
  879. $_post_type['menu_position'] = (int) $post_type->menu_position;
  880. $_post_type['menu_icon'] = $post_type->menu_icon;
  881. $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
  882. }
  883. if ( in_array( 'taxonomies', $fields ) )
  884. $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
  885. /**
  886. * Filters XML-RPC-prepared date for the given post type.
  887. *
  888. * @since 3.4.0
  889. * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
  890. *
  891. * @param array $_post_type An array of post type data.
  892. * @param WP_Post_Type $post_type Post type object.
  893. */
  894. return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
  895. }
  896. /**
  897. * Prepares media item data for return in an XML-RPC object.
  898. *
  899. *
  900. * @param object $media_item The unprepared media item data.
  901. * @param string $thumbnail_size The image size to use for the thumbnail URL.
  902. * @return array The prepared media item data.
  903. */
  904. protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
  905. $_media_item = array(
  906. 'attachment_id' => strval( $media_item->ID ),
  907. 'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
  908. 'parent' => $media_item->post_parent,
  909. 'link' => wp_get_attachment_url( $media_item->ID ),
  910. 'title' => $media_item->post_title,
  911. 'caption' => $media_item->post_excerpt,
  912. 'description' => $media_item->post_content,
  913. 'metadata' => wp_get_attachment_metadata( $media_item->ID ),
  914. 'type' => $media_item->post_mime_type
  915. );
  916. $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
  917. if ( $thumbnail_src )
  918. $_media_item['thumbnail'] = $thumbnail_src[0];
  919. else
  920. $_media_item['thumbnail'] = $_media_item['link'];
  921. /**
  922. * Filters XML-RPC-prepared data for the given media item.
  923. *
  924. * @since 3.4.0
  925. *
  926. * @param array $_media_item An array of media item data.
  927. * @param object $media_item Media item object.
  928. * @param string $thumbnail_size Image size.
  929. */
  930. return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
  931. }
  932. /**
  933. * Prepares page data for return in an XML-RPC object.
  934. *
  935. *
  936. * @param object $page The unprepared page data.
  937. * @return array The prepared page data.
  938. */
  939. protected function _prepare_page( $page ) {
  940. // Get all of the page content and link.
  941. $full_page = get_extended( $page->post_content );
  942. $link = get_permalink( $page->ID );
  943. // Get info the page parent if there is one.
  944. $parent_title = "";
  945. if ( ! empty( $page->post_parent ) ) {
  946. $parent = get_post( $page->post_parent );
  947. $parent_title = $parent->post_title;
  948. }
  949. // Determine comment and ping settings.
  950. $allow_comments = comments_open( $page->ID ) ? 1 : 0;
  951. $allow_pings = pings_open( $page->ID ) ? 1 : 0;
  952. // Format page date.
  953. $page_date = $this->_convert_date( $page->post_date );
  954. $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
  955. // Pull the categories info together.
  956. $categories = array();
  957. if ( is_object_in_taxonomy( 'page', 'category' ) ) {
  958. foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
  959. $categories[] = get_cat_name( $cat_id );
  960. }
  961. }
  962. // Get the author info.
  963. $author = get_userdata( $page->post_author );
  964. $page_template = get_page_template_slug( $page->ID );
  965. if ( empty( $page_template ) )
  966. $page_template = 'default';
  967. $_page = array(
  968. 'dateCreated' => $page_date,
  969. 'userid' => $page->post_author,
  970. 'page_id' => $page->ID,
  971. 'page_status' => $page->post_status,
  972. 'description' => $full_page['main'],
  973. 'title' => $page->post_title,
  974. 'link' => $link,
  975. 'permaLink' => $link,
  976. 'categories' => $categories,
  977. 'excerpt' => $page->post_excerpt,
  978. 'text_more' => $full_page['extended'],
  979. 'mt_allow_comments' => $allow_comments,
  980. 'mt_allow_pings' => $allow_pings,
  981. 'wp_slug' => $page->post_name,
  982. 'wp_password' => $page->post_password,
  983. 'wp_author' => $author->display_name,
  984. 'wp_page_parent_id' => $page->post_parent,
  985. 'wp_page_parent_title' => $parent_title,
  986. 'wp_page_order' => $page->menu_order,
  987. 'wp_author_id' => (string) $author->ID,
  988. 'wp_author_display_name' => $author->display_name,
  989. 'date_created_gmt' => $page_date_gmt,
  990. 'custom_fields' => $this->get_custom_fields( $page->ID ),
  991. 'wp_page_template' => $page_template
  992. );
  993. /**
  994. * Filters XML-RPC-prepared data for the given page.
  995. *
  996. * @since 3.4.0
  997. *
  998. * @param array $_page An array of page data.
  999. * @param WP_Post $page Page object.
  1000. */
  1001. return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
  1002. }
  1003. /**
  1004. * Prepares comment data for return in an XML-RPC object.
  1005. *
  1006. *
  1007. * @param object $comment The unprepared comment data.
  1008. * @return array The prepared comment data.
  1009. */
  1010. protected function _prepare_comment( $comment ) {
  1011. // Format page date.
  1012. $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
  1013. if ( '0' == $comment->comment_approved ) {
  1014. $comment_status = 'hold';
  1015. } elseif ( 'spam' == $comment->comment_approved ) {
  1016. $comment_status = 'spam';
  1017. } elseif ( '1' == $comment->comment_approved ) {
  1018. $comment_status = 'approve';
  1019. } else {
  1020. $comment_status = $comment->comment_approved;
  1021. }
  1022. $_comment = array(
  1023. 'date_created_gmt' => $comment_date_gmt,
  1024. 'user_id' => $comment->user_id,
  1025. 'comment_id' => $comment->comment_ID,
  1026. 'parent' => $comment->comment_parent,
  1027. 'status' => $comment_status,
  1028. 'content' => $comment->comment_content,
  1029. 'link' => get_comment_link($comment),
  1030. 'post_id' => $comment->comment_post_ID,
  1031. 'post_title' => get_the_title($comment->comment_post_ID),
  1032. 'author' => $comment->comment_author,
  1033. 'author_url' => $comment->comment_author_url,
  1034. 'author_email' => $comment->comment_author_email,
  1035. 'author_ip' => $comment->comment_author_IP,
  1036. 'type' => $comment->comment_type,
  1037. );
  1038. /**
  1039. * Filters XML-RPC-prepared data for the given comment.
  1040. *
  1041. * @since 3.4.0
  1042. *
  1043. * @param array $_comment An array of prepared comment data.
  1044. * @param WP_Comment $comment Comment object.
  1045. */
  1046. return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
  1047. }
  1048. /**
  1049. * Prepares user data for return in an XML-RPC object.
  1050. *
  1051. *
  1052. * @param WP_User $user The unprepared user object.
  1053. * @param array $fields The subset of user fields to return.
  1054. * @return array The prepared user data.
  1055. */
  1056. protected function _prepare_user( $user, $fields ) {
  1057. $_user = array( 'user_id' => strval( $user->ID ) );
  1058. $user_fields = array(
  1059. 'username' => $user->user_login,
  1060. 'first_name' => $user->user_firstname,
  1061. 'last_name' => $user->user_lastname,
  1062. 'registered' => $this->_convert_date( $user->user_registered ),
  1063. 'bio' => $user->user_description,
  1064. 'email' => $user->user_email,
  1065. 'nickname' => $user->nickname,
  1066. 'nicename' => $user->user_nicename,
  1067. 'url' => $user->user_url,
  1068. 'display_name' => $user->display_name,
  1069. 'roles' => $user->roles,
  1070. );
  1071. if ( in_array( 'all', $fields ) ) {
  1072. $_user = array_merge( $_user, $user_fields );
  1073. } else {
  1074. if ( in_array( 'basic', $fields ) ) {
  1075. $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
  1076. $fields = array_merge( $fields, $basic_fields );
  1077. }
  1078. $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
  1079. $_user = array_merge( $_user, $requested_fields );
  1080. }
  1081. /**
  1082. * Filters XML-RPC-prepared data for the given user.
  1083. *
  1084. * @since 3.5.0
  1085. *
  1086. * @param array $_user An array of user data.
  1087. * @param WP_User $user User object.
  1088. * @param array $fields An array of user fields.
  1089. */
  1090. return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
  1091. }
  1092. /**
  1093. * Create a new post for any registered post type.
  1094. *
  1095. * @since 3.4.0
  1096. *
  1097. * @link https://en.wikipedia.org/wiki/RSS_enclosure for information on RSS enclosures.
  1098. *
  1099. * @param array $args {
  1100. * Method arguments. Note: top-level arguments must be ordered as documented.
  1101. *
  1102. * @type int $blog_id Blog ID (unused).
  1103. * @type string $username Username.
  1104. * @type string $password Password.
  1105. * @type array $content_struct {
  1106. * Content struct for adding a new post. See wp_insert_post() for information on
  1107. * additional post fields
  1108. *
  1109. * @type string $post_type Post type. Default 'post'.
  1110. * @type string $post_status Post status. Default 'draft'
  1111. * @type string $post_title Post title.
  1112. * @type int $post_author Post author ID.
  1113. * @type string $post_excerpt Post excerpt.
  1114. * @type string $post_content Post content.
  1115. * @type string $post_date_gmt Post date in GMT.
  1116. * @type string $post_date Post date.
  1117. * @type string $post_password Post password (20-character limit).
  1118. * @type string $comment_status Post comment enabled status. Accepts 'open' or 'closed'.
  1119. * @type string $ping_status Post ping status. Accepts 'open' or 'closed'.
  1120. * @type bool $sticky Whether the post should be sticky. Automatically false if
  1121. * `$post_status` is 'private'.
  1122. * @type int $post_thumbnail ID of an image to use as the post thumbnail/featured image.
  1123. * @type array $custom_fields Array of meta key/value pairs to add to the post.
  1124. * @type array $terms Associative array with taxonomy names as keys and arrays
  1125. * of term IDs as values.
  1126. * @type array $terms_names Associative array with taxonomy names as keys and arrays
  1127. * of term names as values.
  1128. * @type array $enclosure {
  1129. * Array of feed enclosure data to add to post meta.
  1130. *
  1131. * @type string $url URL for the feed enclosure.
  1132. * @type int $length Size in bytes of the enclosure.
  1133. * @type string $type Mime-type for the enclosure.
  1134. * }
  1135. * }
  1136. * }
  1137. * @return int|IXR_Error Post ID on success, IXR_Error instance otherwise.
  1138. */
  1139. public function wp_newPost( $args ) {
  1140. if ( ! $this->minimum_args( $args, 4 ) )
  1141. return $this->error;
  1142. $this->escape( $args );
  1143. $username = $args[1];
  1144. $password = $args[2];
  1145. $content_struct = $args[3];
  1146. if ( ! $user = $this->login( $username, $password ) )
  1147. return $this->error;
  1148. // convert the date field back to IXR form
  1149. if ( isset( $content_struct['post_date'] ) && ! ( $content_struct['post_date'] instanceof IXR_Date ) ) {
  1150. $content_struct['post_date'] = $this->_convert_date( $content_struct['post_date'] );
  1151. }
  1152. // ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
  1153. // since _insert_post will ignore the non-GMT date if the GMT date is set
  1154. if ( isset( $content_struct['post_date_gmt'] ) && ! ( $content_struct['post_date_gmt'] instanceof IXR_Date ) ) {
  1155. if ( $content_struct['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) ) {
  1156. unset( $content_struct['post_date_gmt'] );
  1157. } else {
  1158. $content_struct['post_date_gmt'] = $this->_convert_date( $content_struct['post_date_gmt'] );
  1159. }
  1160. }
  1161. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1162. do_action( 'xmlrpc_call', 'wp.newPost' );
  1163. unset( $content_struct['ID'] );
  1164. return $this->_insert_post( $user, $content_struct );
  1165. }
  1166. /**
  1167. * Helper method for filtering out elements from an array.
  1168. *
  1169. * @since 3.4.0
  1170. *
  1171. * @param int $count Number to compare to one.
  1172. */
  1173. private function _is_greater_than_one( $count ) {
  1174. return $count > 1;
  1175. }
  1176. /**
  1177. * Encapsulate the logic for sticking a post
  1178. * and determining if the user has permission to do so
  1179. *
  1180. * @since 4.3.0
  1181. *
  1182. * @param array $post_data
  1183. * @param bool $update
  1184. * @return void|IXR_Error
  1185. */
  1186. private function _toggle_sticky( $post_data, $update = false ) {
  1187. $post_type = get_post_type_object( $post_data['post_type'] );
  1188. // Private and password-protected posts cannot be stickied.
  1189. if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
  1190. // Error if the client tried to stick the post, otherwise, silently unstick.
  1191. if ( ! empty( $post_data['sticky'] ) ) {
  1192. return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
  1193. }
  1194. if ( $update ) {
  1195. unstick_post( $post_data['ID'] );
  1196. }
  1197. } elseif ( isset( $post_data['sticky'] ) ) {
  1198. if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
  1199. return new IXR_Error( 401, __( 'Sorry, you are not allowed to make posts sticky.' ) );
  1200. }
  1201. $sticky = wp_validate_boolean( $post_data['sticky'] );
  1202. if ( $sticky ) {
  1203. stick_post( $post_data['ID'] );
  1204. } else {
  1205. unstick_post( $post_data['ID'] );
  1206. }
  1207. }
  1208. }
  1209. /**
  1210. * Helper method for wp_newPost() and wp_editPost(), containing shared logic.
  1211. *
  1212. * @since 3.4.0
  1213. *
  1214. * @see wp_insert_post()
  1215. *
  1216. * @param WP_User $user The post author if post_author isn't set in $content_struct.
  1217. * @param array|IXR_Error $content_struct Post data to insert.
  1218. * @return IXR_Error|string
  1219. */
  1220. protected function _insert_post( $user, $content_struct ) {
  1221. $defaults = array(
  1222. 'post_status' => 'draft',
  1223. 'post_type' => 'post',
  1224. 'post_author' => null,
  1225. 'post_password' => null,
  1226. 'post_excerpt' => null,
  1227. 'post_content' => null,
  1228. 'post_title' => null,
  1229. 'post_date' => null,
  1230. 'post_date_gmt' => null,
  1231. 'post_format' => null,
  1232. 'post_name' => null,
  1233. 'post_thumbnail' => null,
  1234. 'post_parent' => null,
  1235. 'ping_status' => null,
  1236. 'comment_status' => null,
  1237. 'custom_fields' => null,
  1238. 'terms_names' => null,
  1239. 'terms' => null,
  1240. 'sticky' => null,
  1241. 'enclosure' => null,
  1242. 'ID' => null,
  1243. );
  1244. $post_data = wp_parse_args( array_intersect_key( $content_struct, $defaults ), $defaults );
  1245. $post_type = get_post_type_object( $post_data['post_type'] );
  1246. if ( ! $post_type )
  1247. return new IXR_Error( 403, __( 'Invalid post type.' ) );
  1248. $update = ! empty( $post_data['ID'] );
  1249. if ( $update ) {
  1250. if ( ! get_post( $post_data['ID'] ) )
  1251. return new IXR_Error( 401, __( 'Invalid post ID.' ) );
  1252. if ( ! current_user_can( 'edit_post', $post_data['ID'] ) )
  1253. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  1254. if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
  1255. return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
  1256. } else {
  1257. if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) )
  1258. return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
  1259. }
  1260. switch ( $post_data['post_status'] ) {
  1261. case 'draft':
  1262. case 'pending':
  1263. break;
  1264. case 'private':
  1265. if ( ! current_user_can( $post_type->cap->publish_posts ) )
  1266. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type.' ) );
  1267. break;
  1268. case 'publish':
  1269. case 'future':
  1270. if ( ! current_user_can( $post_type->cap->publish_posts ) )
  1271. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type.' ) );
  1272. break;
  1273. default:
  1274. if ( ! get_post_status_object( $post_data['post_status'] ) )
  1275. $post_data['post_status'] = 'draft';
  1276. break;
  1277. }
  1278. if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
  1279. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type.' ) );
  1280. $post_data['post_author'] = absint( $post_data['post_author'] );
  1281. if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
  1282. if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
  1283. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
  1284. $author = get_userdata( $post_data['post_author'] );
  1285. if ( ! $author )
  1286. return new IXR_Error( 404, __( 'Invalid author ID.' ) );
  1287. } else {
  1288. $post_data['post_author'] = $user->ID;
  1289. }
  1290. if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
  1291. unset( $post_data['comment_status'] );
  1292. if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
  1293. unset( $post_data['ping_status'] );
  1294. // Do some timestamp voodoo.
  1295. if ( ! empty( $post_data['post_date_gmt'] ) ) {
  1296. // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
  1297. $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
  1298. } elseif ( ! empty( $post_data['post_date'] ) ) {
  1299. $dateCreated = $post_data['post_date']->getIso();
  1300. }
  1301. // Default to not flagging the post date to be edited unless it's intentional.
  1302. $post_data['edit_date'] = false;
  1303. if ( ! empty( $dateCreated ) ) {
  1304. $post_data['post_date'] = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
  1305. $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
  1306. // Flag the post date to be edited.
  1307. $post_data['edit_date'] = true;
  1308. }
  1309. if ( ! isset( $post_data['ID'] ) )
  1310. $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
  1311. $post_ID = $post_data['ID'];
  1312. if ( $post_data['post_type'] == 'post' ) {
  1313. $error = $this->_toggle_sticky( $post_data, $update );
  1314. if ( $error ) {
  1315. return $error;
  1316. }
  1317. }
  1318. if ( isset( $post_data['post_thumbnail'] ) ) {
  1319. // empty value deletes, non-empty value adds/updates.
  1320. if ( ! $post_data['post_thumbnail'] )
  1321. delete_post_thumbnail( $post_ID );
  1322. elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
  1323. return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  1324. set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
  1325. unset( $content_struct['post_thumbnail'] );
  1326. }
  1327. if ( isset( $post_data['custom_fields'] ) )
  1328. $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
  1329. if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
  1330. $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
  1331. // Accumulate term IDs from terms and terms_names.
  1332. $terms = array();
  1333. // First validate the terms specified by ID.
  1334. if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
  1335. $taxonomies = array_keys( $post_data['terms'] );
  1336. // Validating term ids.
  1337. foreach ( $taxonomies as $taxonomy ) {
  1338. if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
  1339. return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
  1340. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
  1341. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
  1342. $term_ids = $post_data['terms'][$taxonomy];
  1343. $terms[ $taxonomy ] = array();
  1344. foreach ( $term_ids as $term_id ) {
  1345. $term = get_term_by( 'id', $term_id, $taxonomy );
  1346. if ( ! $term )
  1347. return new IXR_Error( 403, __( 'Invalid term ID.' ) );
  1348. $terms[$taxonomy][] = (int) $term_id;
  1349. }
  1350. }
  1351. }
  1352. // Now validate terms specified by name.
  1353. if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
  1354. $taxonomies = array_keys( $post_data['terms_names'] );
  1355. foreach ( $taxonomies as $taxonomy ) {
  1356. if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
  1357. return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
  1358. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
  1359. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
  1360. /*
  1361. * For hierarchical taxonomies, we can't assign a term when multiple terms
  1362. * in the hierarchy share the same name.
  1363. */
  1364. $ambiguous_terms = array();
  1365. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  1366. $tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
  1367. // Count the number of terms with the same name.
  1368. $tax_term_names_count = array_count_values( $tax_term_names );
  1369. // Filter out non-ambiguous term names.
  1370. $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
  1371. $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
  1372. }
  1373. $term_names = $post_data['terms_names'][$taxonomy];
  1374. foreach ( $term_names as $term_name ) {
  1375. if ( in_array( $term_name, $ambiguous_terms ) )
  1376. return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
  1377. $term = get_term_by( 'name', $term_name, $taxonomy );
  1378. if ( ! $term ) {
  1379. // Term doesn't exist, so check that the user is allowed to create new terms.
  1380. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
  1381. return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
  1382. // Create the new term.
  1383. $term_info = wp_insert_term( $term_name, $taxonomy );
  1384. if ( is_wp_error( $term_info ) )
  1385. return new IXR_Error( 500, $term_info->get_error_message() );
  1386. $terms[$taxonomy][] = (int) $term_info['term_id'];
  1387. } else {
  1388. $terms[$taxonomy][] = (int) $term->term_id;
  1389. }
  1390. }
  1391. }
  1392. }
  1393. $post_data['tax_input'] = $terms;
  1394. unset( $post_data['terms'], $post_data['terms_names'] );
  1395. }
  1396. if ( isset( $post_data['post_format'] ) ) {
  1397. $format = set_post_format( $post_ID, $post_data['post_format'] );
  1398. if ( is_wp_error( $format ) )
  1399. return new IXR_Error( 500, $format->get_error_message() );
  1400. unset( $post_data['post_format'] );
  1401. }
  1402. // Handle enclosures.
  1403. $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
  1404. $this->add_enclosure_if_new( $post_ID, $enclosure );
  1405. $this->attach_uploads( $post_ID, $post_data['post_content'] );
  1406. /**
  1407. * Filters post data array to be inserted via XML-RPC.
  1408. *
  1409. * @since 3.4.0
  1410. *
  1411. * @param array $post_data Parsed array of post data.
  1412. * @param array $content_struct Post data array.
  1413. */
  1414. $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
  1415. $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
  1416. if ( is_wp_error( $post_ID ) )
  1417. return new IXR_Error( 500, $post_ID->get_error_message() );
  1418. if ( ! $post_ID )
  1419. return new IXR_Error( 401, __( 'Sorry, your entry could not be posted.' ) );
  1420. return strval( $post_ID );
  1421. }
  1422. /**
  1423. * Edit a post for any registered post type.
  1424. *
  1425. * The $content_struct parameter only needs to contain fields that
  1426. * should be changed. All other fields will retain their existing values.
  1427. *
  1428. * @since 3.4.0
  1429. *
  1430. * @param array $args {
  1431. * Method arguments. Note: arguments must be ordered as documented.
  1432. *
  1433. * @type int $blog_id Blog ID (unused).
  1434. * @type string $username Username.
  1435. * @type string $password Password.
  1436. * @type int $post_id Post ID.
  1437. * @type array $content_struct Extra content arguments.
  1438. * }
  1439. * @return true|IXR_Error True on success, IXR_Error on failure.
  1440. */
  1441. public function wp_editPost( $args ) {
  1442. if ( ! $this->minimum_args( $args, 5 ) )
  1443. return $this->error;
  1444. $this->escape( $args );
  1445. $username = $args[1];
  1446. $password = $args[2];
  1447. $post_id = (int) $args[3];
  1448. $content_struct = $args[4];
  1449. if ( ! $user = $this->login( $username, $password ) )
  1450. return $this->error;
  1451. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1452. do_action( 'xmlrpc_call', 'wp.editPost' );
  1453. $post = get_post( $post_id, ARRAY_A );
  1454. if ( empty( $post['ID'] ) )
  1455. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1456. if ( isset( $content_struct['if_not_modified_since'] ) ) {
  1457. // If the post has been modified since the date provided, return an error.
  1458. if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
  1459. return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
  1460. }
  1461. }
  1462. // Convert the date field back to IXR form.
  1463. $post['post_date'] = $this->_convert_date( $post['post_date'] );
  1464. /*
  1465. * Ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
  1466. * since _insert_post() will ignore the non-GMT date if the GMT date is set.
  1467. */
  1468. if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
  1469. unset( $post['post_date_gmt'] );
  1470. else
  1471. $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
  1472. $this->escape( $post );
  1473. $merged_content_struct = array_merge( $post, $content_struct );
  1474. $retval = $this->_insert_post( $user, $merged_content_struct );
  1475. if ( $retval instanceof IXR_Error )
  1476. return $retval;
  1477. return true;
  1478. }
  1479. /**
  1480. * Delete a post for any registered post type.
  1481. *
  1482. * @since 3.4.0
  1483. *
  1484. * @see wp_delete_post()
  1485. *
  1486. * @param array $args {
  1487. * Method arguments. Note: arguments must be ordered as documented.
  1488. *
  1489. * @type int $blog_id Blog ID (unused).
  1490. * @type string $username Username.
  1491. * @type string $password Password.
  1492. * @type int $post_id Post ID.
  1493. * }
  1494. * @return true|IXR_Error True on success, IXR_Error instance on failure.
  1495. */
  1496. public function wp_deletePost( $args ) {
  1497. if ( ! $this->minimum_args( $args, 4 ) )
  1498. return $this->error;
  1499. $this->escape( $args );
  1500. $username = $args[1];
  1501. $password = $args[2];
  1502. $post_id = (int) $args[3];
  1503. if ( ! $user = $this->login( $username, $password ) )
  1504. return $this->error;
  1505. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1506. do_action( 'xmlrpc_call', 'wp.deletePost' );
  1507. $post = get_post( $post_id, ARRAY_A );
  1508. if ( empty( $post['ID'] ) ) {
  1509. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1510. }
  1511. if ( ! current_user_can( 'delete_post', $post_id ) ) {
  1512. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
  1513. }
  1514. $result = wp_delete_post( $post_id );
  1515. if ( ! $result ) {
  1516. return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
  1517. }
  1518. return true;
  1519. }
  1520. /**
  1521. * Retrieve a post.
  1522. *
  1523. * @since 3.4.0
  1524. *
  1525. * The optional $fields parameter specifies what fields will be included
  1526. * in the response array. This should be a list of field names. 'post_id' will
  1527. * always be included in the response regardless of the value of $fields.
  1528. *
  1529. * Instead of, or in addition to, individual field names, conceptual group
  1530. * names can be used to specify multiple fields. The available conceptual
  1531. * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
  1532. * and 'enclosure'.
  1533. *
  1534. * @see get_post()
  1535. *
  1536. * @param array $args {
  1537. * Method arguments. Note: arguments must be ordered as documented.
  1538. *
  1539. * @type int $blog_id Blog ID (unused).
  1540. * @type string $username Username.
  1541. * @type string $password Password.
  1542. * @type int $post_id Post ID.
  1543. * @type array $fields The subset of post type fields to return.
  1544. * }
  1545. * @return array|IXR_Error Array contains (based on $fields parameter):
  1546. * - 'post_id'
  1547. * - 'post_title'
  1548. * - 'post_date'
  1549. * - 'post_date_gmt'
  1550. * - 'post_modified'
  1551. * - 'post_modified_gmt'
  1552. * - 'post_status'
  1553. * - 'post_type'
  1554. * - 'post_name'
  1555. * - 'post_author'
  1556. * - 'post_password'
  1557. * - 'post_excerpt'
  1558. * - 'post_content'
  1559. * - 'link'
  1560. * - 'comment_status'
  1561. * - 'ping_status'
  1562. * - 'sticky'
  1563. * - 'custom_fields'
  1564. * - 'terms'
  1565. * - 'categories'
  1566. * - 'tags'
  1567. * - 'enclosure'
  1568. */
  1569. public function wp_getPost( $args ) {
  1570. if ( ! $this->minimum_args( $args, 4 ) )
  1571. return $this->error;
  1572. $this->escape( $args );
  1573. $username = $args[1];
  1574. $password = $args[2];
  1575. $post_id = (int) $args[3];
  1576. if ( isset( $args[4] ) ) {
  1577. $fields = $args[4];
  1578. } else {
  1579. /**
  1580. * Filters the list of post query fields used by the given XML-RPC method.
  1581. *
  1582. * @since 3.4.0
  1583. *
  1584. * @param array $fields Array of post fields. Default array contains 'post', 'terms', and 'custom_fields'.
  1585. * @param string $method Method name.
  1586. */
  1587. $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
  1588. }
  1589. if ( ! $user = $this->login( $username, $password ) )
  1590. return $this->error;
  1591. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1592. do_action( 'xmlrpc_call', 'wp.getPost' );
  1593. $post = get_post( $post_id, ARRAY_A );
  1594. if ( empty( $post['ID'] ) )
  1595. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1596. if ( ! current_user_can( 'edit_post', $post_id ) )
  1597. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  1598. return $this->_prepare_post( $post, $fields );
  1599. }
  1600. /**
  1601. * Retrieve posts.
  1602. *
  1603. * @since 3.4.0
  1604. *
  1605. * @see wp_get_recent_posts()
  1606. * @see wp_getPost() for more on `$fields`
  1607. * @see get_posts() for more on `$filter` values
  1608. *
  1609. * @param array $args {
  1610. * Method arguments. Note: arguments must be ordered as documented.
  1611. *
  1612. * @type int $blog_id Blog ID (unused).
  1613. * @type string $username Username.
  1614. * @type string $password Password.
  1615. * @type array $filter Optional. Modifies the query used to retrieve posts. Accepts 'post_type',
  1616. * 'post_status', 'number', 'offset', 'orderby', 's', and 'order'.
  1617. * Default empty array.
  1618. * @type array $fields Optional. The subset of post type fields to return in the response array.
  1619. * }
  1620. * @return array|IXR_Error Array contains a collection of posts.
  1621. */
  1622. public function wp_getPosts( $args ) {
  1623. if ( ! $this->minimum_args( $args, 3 ) )
  1624. return $this->error;
  1625. $this->escape( $args );
  1626. $username = $args[1];
  1627. $password = $args[2];
  1628. $filter = isset( $args[3] ) ? $args[3] : array();
  1629. if ( isset( $args[4] ) ) {
  1630. $fields = $args[4];
  1631. } else {
  1632. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1633. $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
  1634. }
  1635. if ( ! $user = $this->login( $username, $password ) )
  1636. return $this->error;
  1637. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1638. do_action( 'xmlrpc_call', 'wp.getPosts' );
  1639. $query = array();
  1640. if ( isset( $filter['post_type'] ) ) {
  1641. $post_type = get_post_type_object( $filter['post_type'] );
  1642. if ( ! ( (bool) $post_type ) )
  1643. return new IXR_Error( 403, __( 'Invalid post type.' ) );
  1644. } else {
  1645. $post_type = get_post_type_object( 'post' );
  1646. }
  1647. if ( ! current_user_can( $post_type->cap->edit_posts ) )
  1648. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
  1649. $query['post_type'] = $post_type->name;
  1650. if ( isset( $filter['post_status'] ) )
  1651. $query['post_status'] = $filter['post_status'];
  1652. if ( isset( $filter['number'] ) )
  1653. $query['numberposts'] = absint( $filter['number'] );
  1654. if ( isset( $filter['offset'] ) )
  1655. $query['offset'] = absint( $filter['offset'] );
  1656. if ( isset( $filter['orderby'] ) ) {
  1657. $query['orderby'] = $filter['orderby'];
  1658. if ( isset( $filter['order'] ) )
  1659. $query['order'] = $filter['order'];
  1660. }
  1661. if ( isset( $filter['s'] ) ) {
  1662. $query['s'] = $filter['s'];
  1663. }
  1664. $posts_list = wp_get_recent_posts( $query );
  1665. if ( ! $posts_list )
  1666. return array();
  1667. // Holds all the posts data.
  1668. $struct = array();
  1669. foreach ( $posts_list as $post ) {
  1670. if ( ! current_user_can( 'edit_post', $post['ID'] ) )
  1671. continue;
  1672. $struct[] = $this->_prepare_post( $post, $fields );
  1673. }
  1674. return $struct;
  1675. }
  1676. /**
  1677. * Create a new term.
  1678. *
  1679. * @since 3.4.0
  1680. *
  1681. * @see wp_insert_term()
  1682. *
  1683. * @param array $args {
  1684. * Method arguments. Note: arguments must be ordered as documented.
  1685. *
  1686. * @type int $blog_id Blog ID (unused).
  1687. * @type string $username Username.
  1688. * @type string $password Password.
  1689. * @type array $content_struct Content struct for adding a new term. The struct must contain
  1690. * the term 'name' and 'taxonomy'. Optional accepted values include
  1691. * 'parent', 'description', and 'slug'.
  1692. * }
  1693. * @return int|IXR_Error The term ID on success, or an IXR_Error object on failure.
  1694. */
  1695. public function wp_newTerm( $args ) {
  1696. if ( ! $this->minimum_args( $args, 4 ) )
  1697. return $this->error;
  1698. $this->escape( $args );
  1699. $username = $args[1];
  1700. $password = $args[2];
  1701. $content_struct = $args[3];
  1702. if ( ! $user = $this->login( $username, $password ) )
  1703. return $this->error;
  1704. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1705. do_action( 'xmlrpc_call', 'wp.newTerm' );
  1706. if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
  1707. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  1708. $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
  1709. if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
  1710. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create terms in this taxonomy.' ) );
  1711. }
  1712. $taxonomy = (array) $taxonomy;
  1713. // hold the data of the term
  1714. $term_data = array();
  1715. $term_data['name'] = trim( $content_struct['name'] );
  1716. if ( empty( $term_data['name'] ) )
  1717. return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
  1718. if ( isset( $content_struct['parent'] ) ) {
  1719. if ( ! $taxonomy['hierarchical'] )
  1720. return new IXR_Error( 403, __( 'This taxonomy is not hierarchical.' ) );
  1721. $parent_term_id = (int) $content_struct['parent'];
  1722. $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
  1723. if ( is_wp_error( $parent_term ) )
  1724. return new IXR_Error( 500, $parent_term->get_error_message() );
  1725. if ( ! $parent_term )
  1726. return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
  1727. $term_data['parent'] = $content_struct['parent'];
  1728. }
  1729. if ( isset( $content_struct['description'] ) )
  1730. $term_data['description'] = $content_struct['description'];
  1731. if ( isset( $content_struct['slug'] ) )
  1732. $term_data['slug'] = $content_struct['slug'];
  1733. $term = wp_insert_term( $term_data['name'] , $taxonomy['name'] , $term_data );
  1734. if ( is_wp_error( $term ) )
  1735. return new IXR_Error( 500, $term->get_error_message() );
  1736. if ( ! $term )
  1737. return new IXR_Error( 500, __( 'Sorry, your term could not be created.' ) );
  1738. // Add term meta.
  1739. if ( isset( $content_struct['custom_fields'] ) ) {
  1740. $this->set_term_custom_fields( $term['term_id'], $content_struct['custom_fields'] );
  1741. }
  1742. return strval( $term['term_id'] );
  1743. }
  1744. /**
  1745. * Edit a term.
  1746. *
  1747. * @since 3.4.0
  1748. *
  1749. * @see wp_update_term()
  1750. *
  1751. * @param array $args {
  1752. * Method arguments. Note: arguments must be ordered as documented.
  1753. *
  1754. * @type int $blog_id Blog ID (unused).
  1755. * @type string $username Username.
  1756. * @type string $password Password.
  1757. * @type int $term_id Term ID.
  1758. * @type array $content_struct Content struct for editing a term. The struct must contain the
  1759. * term ''taxonomy'. Optional accepted values include 'name', 'parent',
  1760. * 'description', and 'slug'.
  1761. * }
  1762. * @return true|IXR_Error True on success, IXR_Error instance on failure.
  1763. */
  1764. public function wp_editTerm( $args ) {
  1765. if ( ! $this->minimum_args( $args, 5 ) )
  1766. return $this->error;
  1767. $this->escape( $args );
  1768. $username = $args[1];
  1769. $password = $args[2];
  1770. $term_id = (int) $args[3];
  1771. $content_struct = $args[4];
  1772. if ( ! $user = $this->login( $username, $password ) )
  1773. return $this->error;
  1774. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1775. do_action( 'xmlrpc_call', 'wp.editTerm' );
  1776. if ( ! taxonomy_exists( $content_struct['taxonomy'] ) )
  1777. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  1778. $taxonomy = get_taxonomy( $content_struct['taxonomy'] );
  1779. $taxonomy = (array) $taxonomy;
  1780. // hold the data of the term
  1781. $term_data = array();
  1782. $term = get_term( $term_id , $content_struct['taxonomy'] );
  1783. if ( is_wp_error( $term ) )
  1784. return new IXR_Error( 500, $term->get_error_message() );
  1785. if ( ! $term )
  1786. return new IXR_Error( 404, __( 'Invalid term ID.' ) );
  1787. if ( ! current_user_can( 'edit_term', $term_id ) ) {
  1788. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this term.' ) );
  1789. }
  1790. if ( isset( $content_struct['name'] ) ) {
  1791. $term_data['name'] = trim( $content_struct['name'] );
  1792. if ( empty( $term_data['name'] ) )
  1793. return new IXR_Error( 403, __( 'The term name cannot be empty.' ) );
  1794. }
  1795. if ( ! empty( $content_struct['parent'] ) ) {
  1796. if ( ! $taxonomy['hierarchical'] )
  1797. return new IXR_Error( 403, __( 'Cannot set parent term, taxonomy is not hierarchical.' ) );
  1798. $parent_term_id = (int) $content_struct['parent'];
  1799. $parent_term = get_term( $parent_term_id , $taxonomy['name'] );
  1800. if ( is_wp_error( $parent_term ) )
  1801. return new IXR_Error( 500, $parent_term->get_error_message() );
  1802. if ( ! $parent_term )
  1803. return new IXR_Error( 403, __( 'Parent term does not exist.' ) );
  1804. $term_data['parent'] = $content_struct['parent'];
  1805. }
  1806. if ( isset( $content_struct['description'] ) )
  1807. $term_data['description'] = $content_struct['description'];
  1808. if ( isset( $content_struct['slug'] ) )
  1809. $term_data['slug'] = $content_struct['slug'];
  1810. $term = wp_update_term( $term_id , $taxonomy['name'] , $term_data );
  1811. if ( is_wp_error( $term ) )
  1812. return new IXR_Error( 500, $term->get_error_message() );
  1813. if ( ! $term )
  1814. return new IXR_Error( 500, __( 'Sorry, editing the term failed.' ) );
  1815. // Update term meta.
  1816. if ( isset( $content_struct['custom_fields'] ) ) {
  1817. $this->set_term_custom_fields( $term_id, $content_struct['custom_fields'] );
  1818. }
  1819. return true;
  1820. }
  1821. /**
  1822. * Delete a term.
  1823. *
  1824. * @since 3.4.0
  1825. *
  1826. * @see wp_delete_term()
  1827. *
  1828. * @param array $args {
  1829. * Method arguments. Note: arguments must be ordered as documented.
  1830. *
  1831. * @type int $blog_id Blog ID (unused).
  1832. * @type string $username Username.
  1833. * @type string $password Password.
  1834. * @type string $taxnomy_name Taxonomy name.
  1835. * @type int $term_id Term ID.
  1836. * }
  1837. * @return bool|IXR_Error True on success, IXR_Error instance on failure.
  1838. */
  1839. public function wp_deleteTerm( $args ) {
  1840. if ( ! $this->minimum_args( $args, 5 ) )
  1841. return $this->error;
  1842. $this->escape( $args );
  1843. $username = $args[1];
  1844. $password = $args[2];
  1845. $taxonomy = $args[3];
  1846. $term_id = (int) $args[4];
  1847. if ( ! $user = $this->login( $username, $password ) )
  1848. return $this->error;
  1849. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1850. do_action( 'xmlrpc_call', 'wp.deleteTerm' );
  1851. if ( ! taxonomy_exists( $taxonomy ) )
  1852. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  1853. $taxonomy = get_taxonomy( $taxonomy );
  1854. $term = get_term( $term_id, $taxonomy->name );
  1855. if ( is_wp_error( $term ) )
  1856. return new IXR_Error( 500, $term->get_error_message() );
  1857. if ( ! $term )
  1858. return new IXR_Error( 404, __( 'Invalid term ID.' ) );
  1859. if ( ! current_user_can( 'delete_term', $term_id ) ) {
  1860. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this term.' ) );
  1861. }
  1862. $result = wp_delete_term( $term_id, $taxonomy->name );
  1863. if ( is_wp_error( $result ) )
  1864. return new IXR_Error( 500, $term->get_error_message() );
  1865. if ( ! $result )
  1866. return new IXR_Error( 500, __( 'Sorry, deleting the term failed.' ) );
  1867. return $result;
  1868. }
  1869. /**
  1870. * Retrieve a term.
  1871. *
  1872. * @since 3.4.0
  1873. *
  1874. * @see get_term()
  1875. *
  1876. * @param array $args {
  1877. * Method arguments. Note: arguments must be ordered as documented.
  1878. *
  1879. * @type int $blog_id Blog ID (unused).
  1880. * @type string $username Username.
  1881. * @type string $password Password.
  1882. * @type string $taxnomy Taxonomy name.
  1883. * @type string $term_id Term ID.
  1884. * }
  1885. * @return array|IXR_Error IXR_Error on failure, array on success, containing:
  1886. * - 'term_id'
  1887. * - 'name'
  1888. * - 'slug'
  1889. * - 'term_group'
  1890. * - 'term_taxonomy_id'
  1891. * - 'taxonomy'
  1892. * - 'description'
  1893. * - 'parent'
  1894. * - 'count'
  1895. */
  1896. public function wp_getTerm( $args ) {
  1897. if ( ! $this->minimum_args( $args, 5 ) )
  1898. return $this->error;
  1899. $this->escape( $args );
  1900. $username = $args[1];
  1901. $password = $args[2];
  1902. $taxonomy = $args[3];
  1903. $term_id = (int) $args[4];
  1904. if ( ! $user = $this->login( $username, $password ) )
  1905. return $this->error;
  1906. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1907. do_action( 'xmlrpc_call', 'wp.getTerm' );
  1908. if ( ! taxonomy_exists( $taxonomy ) )
  1909. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  1910. $taxonomy = get_taxonomy( $taxonomy );
  1911. $term = get_term( $term_id , $taxonomy->name, ARRAY_A );
  1912. if ( is_wp_error( $term ) )
  1913. return new IXR_Error( 500, $term->get_error_message() );
  1914. if ( ! $term )
  1915. return new IXR_Error( 404, __( 'Invalid term ID.' ) );
  1916. if ( ! current_user_can( 'assign_term', $term_id ) ) {
  1917. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign this term.' ) );
  1918. }
  1919. return $this->_prepare_term( $term );
  1920. }
  1921. /**
  1922. * Retrieve all terms for a taxonomy.
  1923. *
  1924. * @since 3.4.0
  1925. *
  1926. * The optional $filter parameter modifies the query used to retrieve terms.
  1927. * Accepted keys are 'number', 'offset', 'orderby', 'order', 'hide_empty', and 'search'.
  1928. *
  1929. * @see get_terms()
  1930. *
  1931. * @param array $args {
  1932. * Method arguments. Note: arguments must be ordered as documented.
  1933. *
  1934. * @type int $blog_id Blog ID (unused).
  1935. * @type string $username Username.
  1936. * @type string $password Password.
  1937. * @type string $taxnomy Taxonomy name.
  1938. * @type array $filter Optional. Modifies the query used to retrieve posts. Accepts 'number',
  1939. * 'offset', 'orderby', 'order', 'hide_empty', and 'search'. Default empty array.
  1940. * }
  1941. * @return array|IXR_Error An associative array of terms data on success, IXR_Error instance otherwise.
  1942. */
  1943. public function wp_getTerms( $args ) {
  1944. if ( ! $this->minimum_args( $args, 4 ) )
  1945. return $this->error;
  1946. $this->escape( $args );
  1947. $username = $args[1];
  1948. $password = $args[2];
  1949. $taxonomy = $args[3];
  1950. $filter = isset( $args[4] ) ? $args[4] : array();
  1951. if ( ! $user = $this->login( $username, $password ) )
  1952. return $this->error;
  1953. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  1954. do_action( 'xmlrpc_call', 'wp.getTerms' );
  1955. if ( ! taxonomy_exists( $taxonomy ) )
  1956. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  1957. $taxonomy = get_taxonomy( $taxonomy );
  1958. if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
  1959. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
  1960. $query = array();
  1961. if ( isset( $filter['number'] ) )
  1962. $query['number'] = absint( $filter['number'] );
  1963. if ( isset( $filter['offset'] ) )
  1964. $query['offset'] = absint( $filter['offset'] );
  1965. if ( isset( $filter['orderby'] ) ) {
  1966. $query['orderby'] = $filter['orderby'];
  1967. if ( isset( $filter['order'] ) )
  1968. $query['order'] = $filter['order'];
  1969. }
  1970. if ( isset( $filter['hide_empty'] ) )
  1971. $query['hide_empty'] = $filter['hide_empty'];
  1972. else
  1973. $query['get'] = 'all';
  1974. if ( isset( $filter['search'] ) )
  1975. $query['search'] = $filter['search'];
  1976. $terms = get_terms( $taxonomy->name, $query );
  1977. if ( is_wp_error( $terms ) )
  1978. return new IXR_Error( 500, $terms->get_error_message() );
  1979. $struct = array();
  1980. foreach ( $terms as $term ) {
  1981. $struct[] = $this->_prepare_term( $term );
  1982. }
  1983. return $struct;
  1984. }
  1985. /**
  1986. * Retrieve a taxonomy.
  1987. *
  1988. * @since 3.4.0
  1989. *
  1990. * @see get_taxonomy()
  1991. *
  1992. * @param array $args {
  1993. * Method arguments. Note: arguments must be ordered as documented.
  1994. *
  1995. * @type int $blog_id Blog ID (unused).
  1996. * @type string $username Username.
  1997. * @type string $password Password.
  1998. * @type string $taxnomy Taxonomy name.
  1999. * @type array $fields Optional. Array of taxonomy fields to limit to in the return.
  2000. * Accepts 'labels', 'cap', 'menu', and 'object_type'.
  2001. * Default empty array.
  2002. * }
  2003. * @return array|IXR_Error An array of taxonomy data on success, IXR_Error instance otherwise.
  2004. */
  2005. public function wp_getTaxonomy( $args ) {
  2006. if ( ! $this->minimum_args( $args, 4 ) )
  2007. return $this->error;
  2008. $this->escape( $args );
  2009. $username = $args[1];
  2010. $password = $args[2];
  2011. $taxonomy = $args[3];
  2012. if ( isset( $args[4] ) ) {
  2013. $fields = $args[4];
  2014. } else {
  2015. /**
  2016. * Filters the taxonomy query fields used by the given XML-RPC method.
  2017. *
  2018. * @since 3.4.0
  2019. *
  2020. * @param array $fields An array of taxonomy fields to retrieve.
  2021. * @param string $method The method name.
  2022. */
  2023. $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomy' );
  2024. }
  2025. if ( ! $user = $this->login( $username, $password ) )
  2026. return $this->error;
  2027. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2028. do_action( 'xmlrpc_call', 'wp.getTaxonomy' );
  2029. if ( ! taxonomy_exists( $taxonomy ) )
  2030. return new IXR_Error( 403, __( 'Invalid taxonomy.' ) );
  2031. $taxonomy = get_taxonomy( $taxonomy );
  2032. if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
  2033. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign terms in this taxonomy.' ) );
  2034. return $this->_prepare_taxonomy( $taxonomy, $fields );
  2035. }
  2036. /**
  2037. * Retrieve all taxonomies.
  2038. *
  2039. * @since 3.4.0
  2040. *
  2041. * @see get_taxonomies()
  2042. *
  2043. * @param array $args {
  2044. * Method arguments. Note: arguments must be ordered as documented.
  2045. *
  2046. * @type int $blog_id Blog ID (unused).
  2047. * @type string $username Username.
  2048. * @type string $password Password.
  2049. * @type array $filter Optional. An array of arguments for retrieving taxonomies.
  2050. * @type array $fields Optional. The subset of taxonomy fields to return.
  2051. * }
  2052. * @return array|IXR_Error An associative array of taxonomy data with returned fields determined
  2053. * by `$fields`, or an IXR_Error instance on failure.
  2054. */
  2055. public function wp_getTaxonomies( $args ) {
  2056. if ( ! $this->minimum_args( $args, 3 ) )
  2057. return $this->error;
  2058. $this->escape( $args );
  2059. $username = $args[1];
  2060. $password = $args[2];
  2061. $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true );
  2062. if ( isset( $args[4] ) ) {
  2063. $fields = $args[4];
  2064. } else {
  2065. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2066. $fields = apply_filters( 'xmlrpc_default_taxonomy_fields', array( 'labels', 'cap', 'object_type' ), 'wp.getTaxonomies' );
  2067. }
  2068. if ( ! $user = $this->login( $username, $password ) )
  2069. return $this->error;
  2070. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2071. do_action( 'xmlrpc_call', 'wp.getTaxonomies' );
  2072. $taxonomies = get_taxonomies( $filter, 'objects' );
  2073. // holds all the taxonomy data
  2074. $struct = array();
  2075. foreach ( $taxonomies as $taxonomy ) {
  2076. // capability check for post_types
  2077. if ( ! current_user_can( $taxonomy->cap->assign_terms ) )
  2078. continue;
  2079. $struct[] = $this->_prepare_taxonomy( $taxonomy, $fields );
  2080. }
  2081. return $struct;
  2082. }
  2083. /**
  2084. * Retrieve a user.
  2085. *
  2086. * The optional $fields parameter specifies what fields will be included
  2087. * in the response array. This should be a list of field names. 'user_id' will
  2088. * always be included in the response regardless of the value of $fields.
  2089. *
  2090. * Instead of, or in addition to, individual field names, conceptual group
  2091. * names can be used to specify multiple fields. The available conceptual
  2092. * groups are 'basic' and 'all'.
  2093. *
  2094. * @uses get_userdata()
  2095. *
  2096. * @param array $args {
  2097. * Method arguments. Note: arguments must be ordered as documented.
  2098. *
  2099. * @type int $blog_id (unused)
  2100. * @type string $username
  2101. * @type string $password
  2102. * @type int $user_id
  2103. * @type array $fields (optional)
  2104. * }
  2105. * @return array|IXR_Error Array contains (based on $fields parameter):
  2106. * - 'user_id'
  2107. * - 'username'
  2108. * - 'first_name'
  2109. * - 'last_name'
  2110. * - 'registered'
  2111. * - 'bio'
  2112. * - 'email'
  2113. * - 'nickname'
  2114. * - 'nicename'
  2115. * - 'url'
  2116. * - 'display_name'
  2117. * - 'roles'
  2118. */
  2119. public function wp_getUser( $args ) {
  2120. if ( ! $this->minimum_args( $args, 4 ) )
  2121. return $this->error;
  2122. $this->escape( $args );
  2123. $username = $args[1];
  2124. $password = $args[2];
  2125. $user_id = (int) $args[3];
  2126. if ( isset( $args[4] ) ) {
  2127. $fields = $args[4];
  2128. } else {
  2129. /**
  2130. * Filters the default user query fields used by the given XML-RPC method.
  2131. *
  2132. * @since 3.5.0
  2133. *
  2134. * @param array $fields User query fields for given method. Default 'all'.
  2135. * @param string $method The method name.
  2136. */
  2137. $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUser' );
  2138. }
  2139. if ( ! $user = $this->login( $username, $password ) )
  2140. return $this->error;
  2141. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2142. do_action( 'xmlrpc_call', 'wp.getUser' );
  2143. if ( ! current_user_can( 'edit_user', $user_id ) )
  2144. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this user.' ) );
  2145. $user_data = get_userdata( $user_id );
  2146. if ( ! $user_data )
  2147. return new IXR_Error( 404, __( 'Invalid user ID.' ) );
  2148. return $this->_prepare_user( $user_data, $fields );
  2149. }
  2150. /**
  2151. * Retrieve users.
  2152. *
  2153. * The optional $filter parameter modifies the query used to retrieve users.
  2154. * Accepted keys are 'number' (default: 50), 'offset' (default: 0), 'role',
  2155. * 'who', 'orderby', and 'order'.
  2156. *
  2157. * The optional $fields parameter specifies what fields will be included
  2158. * in the response array.
  2159. *
  2160. * @uses get_users()
  2161. * @see wp_getUser() for more on $fields and return values
  2162. *
  2163. * @param array $args {
  2164. * Method arguments. Note: arguments must be ordered as documented.
  2165. *
  2166. * @type int $blog_id (unused)
  2167. * @type string $username
  2168. * @type string $password
  2169. * @type array $filter (optional)
  2170. * @type array $fields (optional)
  2171. * }
  2172. * @return array|IXR_Error users data
  2173. */
  2174. public function wp_getUsers( $args ) {
  2175. if ( ! $this->minimum_args( $args, 3 ) )
  2176. return $this->error;
  2177. $this->escape( $args );
  2178. $username = $args[1];
  2179. $password = $args[2];
  2180. $filter = isset( $args[3] ) ? $args[3] : array();
  2181. if ( isset( $args[4] ) ) {
  2182. $fields = $args[4];
  2183. } else {
  2184. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2185. $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getUsers' );
  2186. }
  2187. if ( ! $user = $this->login( $username, $password ) )
  2188. return $this->error;
  2189. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2190. do_action( 'xmlrpc_call', 'wp.getUsers' );
  2191. if ( ! current_user_can( 'list_users' ) )
  2192. return new IXR_Error( 401, __( 'Sorry, you are not allowed to list users.' ) );
  2193. $query = array( 'fields' => 'all_with_meta' );
  2194. $query['number'] = ( isset( $filter['number'] ) ) ? absint( $filter['number'] ) : 50;
  2195. $query['offset'] = ( isset( $filter['offset'] ) ) ? absint( $filter['offset'] ) : 0;
  2196. if ( isset( $filter['orderby'] ) ) {
  2197. $query['orderby'] = $filter['orderby'];
  2198. if ( isset( $filter['order'] ) )
  2199. $query['order'] = $filter['order'];
  2200. }
  2201. if ( isset( $filter['role'] ) ) {
  2202. if ( get_role( $filter['role'] ) === null )
  2203. return new IXR_Error( 403, __( 'Invalid role.' ) );
  2204. $query['role'] = $filter['role'];
  2205. }
  2206. if ( isset( $filter['who'] ) ) {
  2207. $query['who'] = $filter['who'];
  2208. }
  2209. $users = get_users( $query );
  2210. $_users = array();
  2211. foreach ( $users as $user_data ) {
  2212. if ( current_user_can( 'edit_user', $user_data->ID ) )
  2213. $_users[] = $this->_prepare_user( $user_data, $fields );
  2214. }
  2215. return $_users;
  2216. }
  2217. /**
  2218. * Retrieve information about the requesting user.
  2219. *
  2220. * @uses get_userdata()
  2221. *
  2222. * @param array $args {
  2223. * Method arguments. Note: arguments must be ordered as documented.
  2224. *
  2225. * @type int $blog_id (unused)
  2226. * @type string $username
  2227. * @type string $password
  2228. * @type array $fields (optional)
  2229. * }
  2230. * @return array|IXR_Error (@see wp_getUser)
  2231. */
  2232. public function wp_getProfile( $args ) {
  2233. if ( ! $this->minimum_args( $args, 3 ) )
  2234. return $this->error;
  2235. $this->escape( $args );
  2236. $username = $args[1];
  2237. $password = $args[2];
  2238. if ( isset( $args[3] ) ) {
  2239. $fields = $args[3];
  2240. } else {
  2241. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2242. $fields = apply_filters( 'xmlrpc_default_user_fields', array( 'all' ), 'wp.getProfile' );
  2243. }
  2244. if ( ! $user = $this->login( $username, $password ) )
  2245. return $this->error;
  2246. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2247. do_action( 'xmlrpc_call', 'wp.getProfile' );
  2248. if ( ! current_user_can( 'edit_user', $user->ID ) )
  2249. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
  2250. $user_data = get_userdata( $user->ID );
  2251. return $this->_prepare_user( $user_data, $fields );
  2252. }
  2253. /**
  2254. * Edit user's profile.
  2255. *
  2256. * @uses wp_update_user()
  2257. *
  2258. * @param array $args {
  2259. * Method arguments. Note: arguments must be ordered as documented.
  2260. *
  2261. * @type int $blog_id (unused)
  2262. * @type string $username
  2263. * @type string $password
  2264. * @type array $content_struct It can optionally contain:
  2265. * - 'first_name'
  2266. * - 'last_name'
  2267. * - 'website'
  2268. * - 'display_name'
  2269. * - 'nickname'
  2270. * - 'nicename'
  2271. * - 'bio'
  2272. * }
  2273. * @return true|IXR_Error True, on success.
  2274. */
  2275. public function wp_editProfile( $args ) {
  2276. if ( ! $this->minimum_args( $args, 4 ) )
  2277. return $this->error;
  2278. $this->escape( $args );
  2279. $username = $args[1];
  2280. $password = $args[2];
  2281. $content_struct = $args[3];
  2282. if ( ! $user = $this->login( $username, $password ) )
  2283. return $this->error;
  2284. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2285. do_action( 'xmlrpc_call', 'wp.editProfile' );
  2286. if ( ! current_user_can( 'edit_user', $user->ID ) )
  2287. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit your profile.' ) );
  2288. // holds data of the user
  2289. $user_data = array();
  2290. $user_data['ID'] = $user->ID;
  2291. // only set the user details if it was given
  2292. if ( isset( $content_struct['first_name'] ) )
  2293. $user_data['first_name'] = $content_struct['first_name'];
  2294. if ( isset( $content_struct['last_name'] ) )
  2295. $user_data['last_name'] = $content_struct['last_name'];
  2296. if ( isset( $content_struct['url'] ) )
  2297. $user_data['user_url'] = $content_struct['url'];
  2298. if ( isset( $content_struct['display_name'] ) )
  2299. $user_data['display_name'] = $content_struct['display_name'];
  2300. if ( isset( $content_struct['nickname'] ) )
  2301. $user_data['nickname'] = $content_struct['nickname'];
  2302. if ( isset( $content_struct['nicename'] ) )
  2303. $user_data['user_nicename'] = $content_struct['nicename'];
  2304. if ( isset( $content_struct['bio'] ) )
  2305. $user_data['description'] = $content_struct['bio'];
  2306. $result = wp_update_user( $user_data );
  2307. if ( is_wp_error( $result ) )
  2308. return new IXR_Error( 500, $result->get_error_message() );
  2309. if ( ! $result )
  2310. return new IXR_Error( 500, __( 'Sorry, the user cannot be updated.' ) );
  2311. return true;
  2312. }
  2313. /**
  2314. * Retrieve page.
  2315. *
  2316. * @since 2.2.0
  2317. *
  2318. * @param array $args {
  2319. * Method arguments. Note: arguments must be ordered as documented.
  2320. *
  2321. * @type int $blog_id (unused)
  2322. * @type int $page_id
  2323. * @type string $username
  2324. * @type string $password
  2325. * }
  2326. * @return array|IXR_Error
  2327. */
  2328. public function wp_getPage( $args ) {
  2329. $this->escape( $args );
  2330. $page_id = (int) $args[1];
  2331. $username = $args[2];
  2332. $password = $args[3];
  2333. if ( !$user = $this->login($username, $password) ) {
  2334. return $this->error;
  2335. }
  2336. $page = get_post($page_id);
  2337. if ( ! $page )
  2338. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  2339. if ( !current_user_can( 'edit_page', $page_id ) )
  2340. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
  2341. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2342. do_action( 'xmlrpc_call', 'wp.getPage' );
  2343. // If we found the page then format the data.
  2344. if ( $page->ID && ($page->post_type == 'page') ) {
  2345. return $this->_prepare_page( $page );
  2346. }
  2347. // If the page doesn't exist indicate that.
  2348. else {
  2349. return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
  2350. }
  2351. }
  2352. /**
  2353. * Retrieve Pages.
  2354. *
  2355. * @since 2.2.0
  2356. *
  2357. * @param array $args {
  2358. * Method arguments. Note: arguments must be ordered as documented.
  2359. *
  2360. * @type int $blog_id (unused)
  2361. * @type string $username
  2362. * @type string $password
  2363. * @type int $num_pages
  2364. * }
  2365. * @return array|IXR_Error
  2366. */
  2367. public function wp_getPages( $args ) {
  2368. $this->escape( $args );
  2369. $username = $args[1];
  2370. $password = $args[2];
  2371. $num_pages = isset($args[3]) ? (int) $args[3] : 10;
  2372. if ( !$user = $this->login($username, $password) )
  2373. return $this->error;
  2374. if ( !current_user_can( 'edit_pages' ) )
  2375. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
  2376. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2377. do_action( 'xmlrpc_call', 'wp.getPages' );
  2378. $pages = get_posts( array('post_type' => 'page', 'post_status' => 'any', 'numberposts' => $num_pages) );
  2379. $num_pages = count($pages);
  2380. // If we have pages, put together their info.
  2381. if ( $num_pages >= 1 ) {
  2382. $pages_struct = array();
  2383. foreach ($pages as $page) {
  2384. if ( current_user_can( 'edit_page', $page->ID ) )
  2385. $pages_struct[] = $this->_prepare_page( $page );
  2386. }
  2387. return $pages_struct;
  2388. }
  2389. return array();
  2390. }
  2391. /**
  2392. * Create new page.
  2393. *
  2394. * @since 2.2.0
  2395. *
  2396. * @see wp_xmlrpc_server::mw_newPost()
  2397. *
  2398. * @param array $args {
  2399. * Method arguments. Note: arguments must be ordered as documented.
  2400. *
  2401. * @type int $blog_id (unused)
  2402. * @type string $username
  2403. * @type string $password
  2404. * @type array $content_struct
  2405. * }
  2406. * @return int|IXR_Error
  2407. */
  2408. public function wp_newPage( $args ) {
  2409. // Items not escaped here will be escaped in newPost.
  2410. $username = $this->escape( $args[1] );
  2411. $password = $this->escape( $args[2] );
  2412. if ( !$user = $this->login($username, $password) )
  2413. return $this->error;
  2414. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2415. do_action( 'xmlrpc_call', 'wp.newPage' );
  2416. // Mark this as content for a page.
  2417. $args[3]["post_type"] = 'page';
  2418. // Let mw_newPost do all of the heavy lifting.
  2419. return $this->mw_newPost( $args );
  2420. }
  2421. /**
  2422. * Delete page.
  2423. *
  2424. * @since 2.2.0
  2425. *
  2426. * @param array $args {
  2427. * Method arguments. Note: arguments must be ordered as documented.
  2428. *
  2429. * @type int $blog_id (unused)
  2430. * @type string $username
  2431. * @type string $password
  2432. * @type int $page_id
  2433. * }
  2434. * @return true|IXR_Error True, if success.
  2435. */
  2436. public function wp_deletePage( $args ) {
  2437. $this->escape( $args );
  2438. $username = $args[1];
  2439. $password = $args[2];
  2440. $page_id = (int) $args[3];
  2441. if ( !$user = $this->login($username, $password) )
  2442. return $this->error;
  2443. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2444. do_action( 'xmlrpc_call', 'wp.deletePage' );
  2445. // Get the current page based on the page_id and
  2446. // make sure it is a page and not a post.
  2447. $actual_page = get_post($page_id, ARRAY_A);
  2448. if ( !$actual_page || ($actual_page['post_type'] != 'page') )
  2449. return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
  2450. // Make sure the user can delete pages.
  2451. if ( !current_user_can('delete_page', $page_id) )
  2452. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this page.' ) );
  2453. // Attempt to delete the page.
  2454. $result = wp_delete_post($page_id);
  2455. if ( !$result )
  2456. return new IXR_Error( 500, __( 'Failed to delete the page.' ) );
  2457. /**
  2458. * Fires after a page has been successfully deleted via XML-RPC.
  2459. *
  2460. * @since 3.4.0
  2461. *
  2462. * @param int $page_id ID of the deleted page.
  2463. * @param array $args An array of arguments to delete the page.
  2464. */
  2465. do_action( 'xmlrpc_call_success_wp_deletePage', $page_id, $args );
  2466. return true;
  2467. }
  2468. /**
  2469. * Edit page.
  2470. *
  2471. * @since 2.2.0
  2472. *
  2473. * @param array $args {
  2474. * Method arguments. Note: arguments must be ordered as documented.
  2475. *
  2476. * @type int $blog_id (unused)
  2477. * @type int $page_id
  2478. * @type string $username
  2479. * @type string $password
  2480. * @type string $content
  2481. * @type string $publish
  2482. * }
  2483. * @return array|IXR_Error
  2484. */
  2485. public function wp_editPage( $args ) {
  2486. // Items will be escaped in mw_editPost.
  2487. $page_id = (int) $args[1];
  2488. $username = $args[2];
  2489. $password = $args[3];
  2490. $content = $args[4];
  2491. $publish = $args[5];
  2492. $escaped_username = $this->escape( $username );
  2493. $escaped_password = $this->escape( $password );
  2494. if ( !$user = $this->login( $escaped_username, $escaped_password ) ) {
  2495. return $this->error;
  2496. }
  2497. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2498. do_action( 'xmlrpc_call', 'wp.editPage' );
  2499. // Get the page data and make sure it is a page.
  2500. $actual_page = get_post($page_id, ARRAY_A);
  2501. if ( !$actual_page || ($actual_page['post_type'] != 'page') )
  2502. return new IXR_Error( 404, __( 'Sorry, no such page.' ) );
  2503. // Make sure the user is allowed to edit pages.
  2504. if ( !current_user_can('edit_page', $page_id) )
  2505. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this page.' ) );
  2506. // Mark this as content for a page.
  2507. $content['post_type'] = 'page';
  2508. // Arrange args in the way mw_editPost understands.
  2509. $args = array(
  2510. $page_id,
  2511. $username,
  2512. $password,
  2513. $content,
  2514. $publish
  2515. );
  2516. // Let mw_editPost do all of the heavy lifting.
  2517. return $this->mw_editPost( $args );
  2518. }
  2519. /**
  2520. * Retrieve page list.
  2521. *
  2522. * @since 2.2.0
  2523. *
  2524. * @global wpdb $wpdb WordPress database abstraction object.
  2525. *
  2526. * @param array $args {
  2527. * Method arguments. Note: arguments must be ordered as documented.
  2528. *
  2529. * @type int $blog_id (unused)
  2530. * @type string $username
  2531. * @type string $password
  2532. * }
  2533. * @return array|IXR_Error
  2534. */
  2535. public function wp_getPageList( $args ) {
  2536. global $wpdb;
  2537. $this->escape( $args );
  2538. $username = $args[1];
  2539. $password = $args[2];
  2540. if ( !$user = $this->login($username, $password) )
  2541. return $this->error;
  2542. if ( !current_user_can( 'edit_pages' ) )
  2543. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit pages.' ) );
  2544. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2545. do_action( 'xmlrpc_call', 'wp.getPageList' );
  2546. // Get list of pages ids and titles
  2547. $page_list = $wpdb->get_results("
  2548. SELECT ID page_id,
  2549. post_title page_title,
  2550. post_parent page_parent_id,
  2551. post_date_gmt,
  2552. post_date,
  2553. post_status
  2554. FROM {$wpdb->posts}
  2555. WHERE post_type = 'page'
  2556. ORDER BY ID
  2557. ");
  2558. // The date needs to be formatted properly.
  2559. $num_pages = count($page_list);
  2560. for ( $i = 0; $i < $num_pages; $i++ ) {
  2561. $page_list[$i]->dateCreated = $this->_convert_date( $page_list[$i]->post_date );
  2562. $page_list[$i]->date_created_gmt = $this->_convert_date_gmt( $page_list[$i]->post_date_gmt, $page_list[$i]->post_date );
  2563. unset($page_list[$i]->post_date_gmt);
  2564. unset($page_list[$i]->post_date);
  2565. unset($page_list[$i]->post_status);
  2566. }
  2567. return $page_list;
  2568. }
  2569. /**
  2570. * Retrieve authors list.
  2571. *
  2572. * @since 2.2.0
  2573. *
  2574. * @param array $args {
  2575. * Method arguments. Note: arguments must be ordered as documented.
  2576. *
  2577. * @type int $blog_id (unused)
  2578. * @type string $username
  2579. * @type string $password
  2580. * }
  2581. * @return array|IXR_Error
  2582. */
  2583. public function wp_getAuthors( $args ) {
  2584. $this->escape( $args );
  2585. $username = $args[1];
  2586. $password = $args[2];
  2587. if ( !$user = $this->login($username, $password) )
  2588. return $this->error;
  2589. if ( !current_user_can('edit_posts') )
  2590. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
  2591. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2592. do_action( 'xmlrpc_call', 'wp.getAuthors' );
  2593. $authors = array();
  2594. foreach ( get_users( array( 'fields' => array('ID','user_login','display_name') ) ) as $user ) {
  2595. $authors[] = array(
  2596. 'user_id' => $user->ID,
  2597. 'user_login' => $user->user_login,
  2598. 'display_name' => $user->display_name
  2599. );
  2600. }
  2601. return $authors;
  2602. }
  2603. /**
  2604. * Get list of all tags
  2605. *
  2606. * @since 2.7.0
  2607. *
  2608. * @param array $args {
  2609. * Method arguments. Note: arguments must be ordered as documented.
  2610. *
  2611. * @type int $blog_id (unused)
  2612. * @type string $username
  2613. * @type string $password
  2614. * }
  2615. * @return array|IXR_Error
  2616. */
  2617. public function wp_getTags( $args ) {
  2618. $this->escape( $args );
  2619. $username = $args[1];
  2620. $password = $args[2];
  2621. if ( !$user = $this->login($username, $password) )
  2622. return $this->error;
  2623. if ( !current_user_can( 'edit_posts' ) )
  2624. return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view tags.' ) );
  2625. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2626. do_action( 'xmlrpc_call', 'wp.getKeywords' );
  2627. $tags = array();
  2628. if ( $all_tags = get_tags() ) {
  2629. foreach ( (array) $all_tags as $tag ) {
  2630. $struct = array();
  2631. $struct['tag_id'] = $tag->term_id;
  2632. $struct['name'] = $tag->name;
  2633. $struct['count'] = $tag->count;
  2634. $struct['slug'] = $tag->slug;
  2635. $struct['html_url'] = esc_html( get_tag_link( $tag->term_id ) );
  2636. $struct['rss_url'] = esc_html( get_tag_feed_link( $tag->term_id ) );
  2637. $tags[] = $struct;
  2638. }
  2639. }
  2640. return $tags;
  2641. }
  2642. /**
  2643. * Create new category.
  2644. *
  2645. * @since 2.2.0
  2646. *
  2647. * @param array $args {
  2648. * Method arguments. Note: arguments must be ordered as documented.
  2649. *
  2650. * @type int $blog_id (unused)
  2651. * @type string $username
  2652. * @type string $password
  2653. * @type array $category
  2654. * }
  2655. * @return int|IXR_Error Category ID.
  2656. */
  2657. public function wp_newCategory( $args ) {
  2658. $this->escape( $args );
  2659. $username = $args[1];
  2660. $password = $args[2];
  2661. $category = $args[3];
  2662. if ( !$user = $this->login($username, $password) )
  2663. return $this->error;
  2664. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2665. do_action( 'xmlrpc_call', 'wp.newCategory' );
  2666. // Make sure the user is allowed to add a category.
  2667. if ( ! current_user_can( 'manage_categories' ) ) {
  2668. return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a category.' ) );
  2669. }
  2670. // If no slug was provided make it empty so that
  2671. // WordPress will generate one.
  2672. if ( empty($category['slug']) )
  2673. $category['slug'] = '';
  2674. // If no parent_id was provided make it empty
  2675. // so that it will be a top level page (no parent).
  2676. if ( !isset($category['parent_id']) )
  2677. $category['parent_id'] = '';
  2678. // If no description was provided make it empty.
  2679. if ( empty($category["description"]) )
  2680. $category["description"] = "";
  2681. $new_category = array(
  2682. 'cat_name' => $category['name'],
  2683. 'category_nicename' => $category['slug'],
  2684. 'category_parent' => $category['parent_id'],
  2685. 'category_description' => $category['description']
  2686. );
  2687. $cat_id = wp_insert_category($new_category, true);
  2688. if ( is_wp_error( $cat_id ) ) {
  2689. if ( 'term_exists' == $cat_id->get_error_code() )
  2690. return (int) $cat_id->get_error_data();
  2691. else
  2692. return new IXR_Error(500, __('Sorry, the new category failed.'));
  2693. } elseif ( ! $cat_id ) {
  2694. return new IXR_Error(500, __('Sorry, the new category failed.'));
  2695. }
  2696. /**
  2697. * Fires after a new category has been successfully created via XML-RPC.
  2698. *
  2699. * @since 3.4.0
  2700. *
  2701. * @param int $cat_id ID of the new category.
  2702. * @param array $args An array of new category arguments.
  2703. */
  2704. do_action( 'xmlrpc_call_success_wp_newCategory', $cat_id, $args );
  2705. return $cat_id;
  2706. }
  2707. /**
  2708. * Remove category.
  2709. *
  2710. * @since 2.5.0
  2711. *
  2712. * @param array $args {
  2713. * Method arguments. Note: arguments must be ordered as documented.
  2714. *
  2715. * @type int $blog_id (unused)
  2716. * @type string $username
  2717. * @type string $password
  2718. * @type int $category_id
  2719. * }
  2720. * @return bool|IXR_Error See wp_delete_term() for return info.
  2721. */
  2722. public function wp_deleteCategory( $args ) {
  2723. $this->escape( $args );
  2724. $username = $args[1];
  2725. $password = $args[2];
  2726. $category_id = (int) $args[3];
  2727. if ( !$user = $this->login($username, $password) )
  2728. return $this->error;
  2729. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2730. do_action( 'xmlrpc_call', 'wp.deleteCategory' );
  2731. if ( ! current_user_can( 'delete_term', $category_id ) ) {
  2732. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this category.' ) );
  2733. }
  2734. $status = wp_delete_term( $category_id, 'category' );
  2735. if ( true == $status ) {
  2736. /**
  2737. * Fires after a category has been successfully deleted via XML-RPC.
  2738. *
  2739. * @since 3.4.0
  2740. *
  2741. * @param int $category_id ID of the deleted category.
  2742. * @param array $args An array of arguments to delete the category.
  2743. */
  2744. do_action( 'xmlrpc_call_success_wp_deleteCategory', $category_id, $args );
  2745. }
  2746. return $status;
  2747. }
  2748. /**
  2749. * Retrieve category list.
  2750. *
  2751. * @since 2.2.0
  2752. *
  2753. * @param array $args {
  2754. * Method arguments. Note: arguments must be ordered as documented.
  2755. *
  2756. * @type int $blog_id (unused)
  2757. * @type string $username
  2758. * @type string $password
  2759. * @type array $category
  2760. * @type int $max_results
  2761. * }
  2762. * @return array|IXR_Error
  2763. */
  2764. public function wp_suggestCategories( $args ) {
  2765. $this->escape( $args );
  2766. $username = $args[1];
  2767. $password = $args[2];
  2768. $category = $args[3];
  2769. $max_results = (int) $args[4];
  2770. if ( !$user = $this->login($username, $password) )
  2771. return $this->error;
  2772. if ( !current_user_can( 'edit_posts' ) )
  2773. return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
  2774. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2775. do_action( 'xmlrpc_call', 'wp.suggestCategories' );
  2776. $category_suggestions = array();
  2777. $args = array('get' => 'all', 'number' => $max_results, 'name__like' => $category);
  2778. foreach ( (array) get_categories($args) as $cat ) {
  2779. $category_suggestions[] = array(
  2780. 'category_id' => $cat->term_id,
  2781. 'category_name' => $cat->name
  2782. );
  2783. }
  2784. return $category_suggestions;
  2785. }
  2786. /**
  2787. * Retrieve comment.
  2788. *
  2789. * @since 2.7.0
  2790. *
  2791. * @param array $args {
  2792. * Method arguments. Note: arguments must be ordered as documented.
  2793. *
  2794. * @type int $blog_id (unused)
  2795. * @type string $username
  2796. * @type string $password
  2797. * @type int $comment_id
  2798. * }
  2799. * @return array|IXR_Error
  2800. */
  2801. public function wp_getComment($args) {
  2802. $this->escape($args);
  2803. $username = $args[1];
  2804. $password = $args[2];
  2805. $comment_id = (int) $args[3];
  2806. if ( ! $user = $this->login( $username, $password ) ) {
  2807. return $this->error;
  2808. }
  2809. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2810. do_action( 'xmlrpc_call', 'wp.getComment' );
  2811. if ( ! $comment = get_comment( $comment_id ) ) {
  2812. return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
  2813. }
  2814. if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
  2815. return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
  2816. }
  2817. return $this->_prepare_comment( $comment );
  2818. }
  2819. /**
  2820. * Retrieve comments.
  2821. *
  2822. * Besides the common blog_id (unused), username, and password arguments, it takes a filter
  2823. * array as last argument.
  2824. *
  2825. * Accepted 'filter' keys are 'status', 'post_id', 'offset', and 'number'.
  2826. *
  2827. * The defaults are as follows:
  2828. * - 'status' - Default is ''. Filter by status (e.g., 'approve', 'hold')
  2829. * - 'post_id' - Default is ''. The post where the comment is posted. Empty string shows all comments.
  2830. * - 'number' - Default is 10. Total number of media items to retrieve.
  2831. * - 'offset' - Default is 0. See WP_Query::query() for more.
  2832. *
  2833. * @since 2.7.0
  2834. *
  2835. * @param array $args {
  2836. * Method arguments. Note: arguments must be ordered as documented.
  2837. *
  2838. * @type int $blog_id (unused)
  2839. * @type string $username
  2840. * @type string $password
  2841. * @type array $struct
  2842. * }
  2843. * @return array|IXR_Error Contains a collection of comments. See wp_xmlrpc_server::wp_getComment() for a description of each item contents
  2844. */
  2845. public function wp_getComments( $args ) {
  2846. $this->escape( $args );
  2847. $username = $args[1];
  2848. $password = $args[2];
  2849. $struct = isset( $args[3] ) ? $args[3] : array();
  2850. if ( ! $user = $this->login( $username, $password ) ) {
  2851. return $this->error;
  2852. }
  2853. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2854. do_action( 'xmlrpc_call', 'wp.getComments' );
  2855. if ( isset( $struct['status'] ) ) {
  2856. $status = $struct['status'];
  2857. } else {
  2858. $status = '';
  2859. }
  2860. if ( ! current_user_can( 'moderate_comments' ) && 'approve' !== $status ) {
  2861. return new IXR_Error( 401, __( 'Invalid comment status.' ) );
  2862. }
  2863. $post_id = '';
  2864. if ( isset( $struct['post_id'] ) ) {
  2865. $post_id = absint( $struct['post_id'] );
  2866. }
  2867. $post_type = '';
  2868. if ( isset( $struct['post_type'] ) ) {
  2869. $post_type_object = get_post_type_object( $struct['post_type'] );
  2870. if ( ! $post_type_object || ! post_type_supports( $post_type_object->name, 'comments' ) ) {
  2871. return new IXR_Error( 404, __( 'Invalid post type.' ) );
  2872. }
  2873. $post_type = $struct['post_type'];
  2874. }
  2875. $offset = 0;
  2876. if ( isset( $struct['offset'] ) ) {
  2877. $offset = absint( $struct['offset'] );
  2878. }
  2879. $number = 10;
  2880. if ( isset( $struct['number'] ) ) {
  2881. $number = absint( $struct['number'] );
  2882. }
  2883. $comments = get_comments( array(
  2884. 'status' => $status,
  2885. 'post_id' => $post_id,
  2886. 'offset' => $offset,
  2887. 'number' => $number,
  2888. 'post_type' => $post_type,
  2889. ) );
  2890. $comments_struct = array();
  2891. if ( is_array( $comments ) ) {
  2892. foreach ( $comments as $comment ) {
  2893. $comments_struct[] = $this->_prepare_comment( $comment );
  2894. }
  2895. }
  2896. return $comments_struct;
  2897. }
  2898. /**
  2899. * Delete a comment.
  2900. *
  2901. * By default, the comment will be moved to the trash instead of deleted.
  2902. * See wp_delete_comment() for more information on this behavior.
  2903. *
  2904. * @since 2.7.0
  2905. *
  2906. * @param array $args {
  2907. * Method arguments. Note: arguments must be ordered as documented.
  2908. *
  2909. * @type int $blog_id (unused)
  2910. * @type string $username
  2911. * @type string $password
  2912. * @type int $comment_ID
  2913. * }
  2914. * @return bool|IXR_Error See wp_delete_comment().
  2915. */
  2916. public function wp_deleteComment( $args ) {
  2917. $this->escape($args);
  2918. $username = $args[1];
  2919. $password = $args[2];
  2920. $comment_ID = (int) $args[3];
  2921. if ( ! $user = $this->login( $username, $password ) ) {
  2922. return $this->error;
  2923. }
  2924. if ( ! get_comment( $comment_ID ) ) {
  2925. return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
  2926. }
  2927. if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
  2928. return new IXR_Error( 403, __( 'Sorry, you are not allowed to delete this comment.' ) );
  2929. }
  2930. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2931. do_action( 'xmlrpc_call', 'wp.deleteComment' );
  2932. $status = wp_delete_comment( $comment_ID );
  2933. if ( $status ) {
  2934. /**
  2935. * Fires after a comment has been successfully deleted via XML-RPC.
  2936. *
  2937. * @since 3.4.0
  2938. *
  2939. * @param int $comment_ID ID of the deleted comment.
  2940. * @param array $args An array of arguments to delete the comment.
  2941. */
  2942. do_action( 'xmlrpc_call_success_wp_deleteComment', $comment_ID, $args );
  2943. }
  2944. return $status;
  2945. }
  2946. /**
  2947. * Edit comment.
  2948. *
  2949. * Besides the common blog_id (unused), username, and password arguments, it takes a
  2950. * comment_id integer and a content_struct array as last argument.
  2951. *
  2952. * The allowed keys in the content_struct array are:
  2953. * - 'author'
  2954. * - 'author_url'
  2955. * - 'author_email'
  2956. * - 'content'
  2957. * - 'date_created_gmt'
  2958. * - 'status'. Common statuses are 'approve', 'hold', 'spam'. See get_comment_statuses() for more details
  2959. *
  2960. * @since 2.7.0
  2961. *
  2962. * @param array $args {
  2963. * Method arguments. Note: arguments must be ordered as documented.
  2964. *
  2965. * @type int $blog_id (unused)
  2966. * @type string $username
  2967. * @type string $password
  2968. * @type int $comment_ID
  2969. * @type array $content_struct
  2970. * }
  2971. * @return true|IXR_Error True, on success.
  2972. */
  2973. public function wp_editComment( $args ) {
  2974. $this->escape( $args );
  2975. $username = $args[1];
  2976. $password = $args[2];
  2977. $comment_ID = (int) $args[3];
  2978. $content_struct = $args[4];
  2979. if ( !$user = $this->login( $username, $password ) ) {
  2980. return $this->error;
  2981. }
  2982. if ( ! get_comment( $comment_ID ) ) {
  2983. return new IXR_Error( 404, __( 'Invalid comment ID.' ) );
  2984. }
  2985. if ( ! current_user_can( 'edit_comment', $comment_ID ) ) {
  2986. return new IXR_Error( 403, __( 'Sorry, you are not allowed to moderate or edit this comment.' ) );
  2987. }
  2988. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  2989. do_action( 'xmlrpc_call', 'wp.editComment' );
  2990. if ( isset($content_struct['status']) ) {
  2991. $statuses = get_comment_statuses();
  2992. $statuses = array_keys($statuses);
  2993. if ( ! in_array($content_struct['status'], $statuses) )
  2994. return new IXR_Error( 401, __( 'Invalid comment status.' ) );
  2995. $comment_approved = $content_struct['status'];
  2996. }
  2997. // Do some timestamp voodoo
  2998. if ( !empty( $content_struct['date_created_gmt'] ) ) {
  2999. // We know this is supposed to be GMT, so we're going to slap that Z on there by force
  3000. $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
  3001. $comment_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
  3002. $comment_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
  3003. }
  3004. if ( isset($content_struct['content']) )
  3005. $comment_content = $content_struct['content'];
  3006. if ( isset($content_struct['author']) )
  3007. $comment_author = $content_struct['author'];
  3008. if ( isset($content_struct['author_url']) )
  3009. $comment_author_url = $content_struct['author_url'];
  3010. if ( isset($content_struct['author_email']) )
  3011. $comment_author_email = $content_struct['author_email'];
  3012. // We've got all the data -- post it:
  3013. $comment = compact('comment_ID', 'comment_content', 'comment_approved', 'comment_date', 'comment_date_gmt', 'comment_author', 'comment_author_email', 'comment_author_url');
  3014. $result = wp_update_comment($comment);
  3015. if ( is_wp_error( $result ) )
  3016. return new IXR_Error(500, $result->get_error_message());
  3017. if ( !$result )
  3018. return new IXR_Error(500, __('Sorry, the comment could not be edited.'));
  3019. /**
  3020. * Fires after a comment has been successfully updated via XML-RPC.
  3021. *
  3022. * @since 3.4.0
  3023. *
  3024. * @param int $comment_ID ID of the updated comment.
  3025. * @param array $args An array of arguments to update the comment.
  3026. */
  3027. do_action( 'xmlrpc_call_success_wp_editComment', $comment_ID, $args );
  3028. return true;
  3029. }
  3030. /**
  3031. * Create new comment.
  3032. *
  3033. * @since 2.7.0
  3034. *
  3035. * @param array $args {
  3036. * Method arguments. Note: arguments must be ordered as documented.
  3037. *
  3038. * @type int $blog_id (unused)
  3039. * @type string $username
  3040. * @type string $password
  3041. * @type string|int $post
  3042. * @type array $content_struct
  3043. * }
  3044. * @return int|IXR_Error See wp_new_comment().
  3045. */
  3046. public function wp_newComment($args) {
  3047. $this->escape($args);
  3048. $username = $args[1];
  3049. $password = $args[2];
  3050. $post = $args[3];
  3051. $content_struct = $args[4];
  3052. /**
  3053. * Filters whether to allow anonymous comments over XML-RPC.
  3054. *
  3055. * @since 2.7.0
  3056. *
  3057. * @param bool $allow Whether to allow anonymous commenting via XML-RPC.
  3058. * Default false.
  3059. */
  3060. $allow_anon = apply_filters( 'xmlrpc_allow_anonymous_comments', false );
  3061. $user = $this->login($username, $password);
  3062. if ( !$user ) {
  3063. $logged_in = false;
  3064. if ( $allow_anon && get_option('comment_registration') ) {
  3065. return new IXR_Error( 403, __( 'You must be registered to comment.' ) );
  3066. } elseif ( ! $allow_anon ) {
  3067. return $this->error;
  3068. }
  3069. } else {
  3070. $logged_in = true;
  3071. }
  3072. if ( is_numeric($post) )
  3073. $post_id = absint($post);
  3074. else
  3075. $post_id = url_to_postid($post);
  3076. if ( ! $post_id ) {
  3077. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3078. }
  3079. if ( ! get_post( $post_id ) ) {
  3080. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3081. }
  3082. if ( ! comments_open( $post_id ) ) {
  3083. return new IXR_Error( 403, __( 'Sorry, comments are closed for this item.' ) );
  3084. }
  3085. if ( empty( $content_struct['content'] ) ) {
  3086. return new IXR_Error( 403, __( 'Comment is required.' ) );
  3087. }
  3088. $comment = array(
  3089. 'comment_post_ID' => $post_id,
  3090. 'comment_content' => $content_struct['content'],
  3091. );
  3092. if ( $logged_in ) {
  3093. $display_name = $user->display_name;
  3094. $user_email = $user->user_email;
  3095. $user_url = $user->user_url;
  3096. $comment['comment_author'] = $this->escape( $display_name );
  3097. $comment['comment_author_email'] = $this->escape( $user_email );
  3098. $comment['comment_author_url'] = $this->escape( $user_url );
  3099. $comment['user_ID'] = $user->ID;
  3100. } else {
  3101. $comment['comment_author'] = '';
  3102. if ( isset($content_struct['author']) )
  3103. $comment['comment_author'] = $content_struct['author'];
  3104. $comment['comment_author_email'] = '';
  3105. if ( isset($content_struct['author_email']) )
  3106. $comment['comment_author_email'] = $content_struct['author_email'];
  3107. $comment['comment_author_url'] = '';
  3108. if ( isset($content_struct['author_url']) )
  3109. $comment['comment_author_url'] = $content_struct['author_url'];
  3110. $comment['user_ID'] = 0;
  3111. if ( get_option('require_name_email') ) {
  3112. if ( 6 > strlen($comment['comment_author_email']) || '' == $comment['comment_author'] )
  3113. return new IXR_Error( 403, __( 'Comment author name and email are required.' ) );
  3114. elseif ( !is_email($comment['comment_author_email']) )
  3115. return new IXR_Error( 403, __( 'A valid email address is required.' ) );
  3116. }
  3117. }
  3118. $comment['comment_parent'] = isset($content_struct['comment_parent']) ? absint($content_struct['comment_parent']) : 0;
  3119. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3120. do_action( 'xmlrpc_call', 'wp.newComment' );
  3121. $comment_ID = wp_new_comment( $comment, true );
  3122. if ( is_wp_error( $comment_ID ) ) {
  3123. return new IXR_Error( 403, $comment_ID->get_error_message() );
  3124. }
  3125. if ( ! $comment_ID ) {
  3126. return new IXR_Error( 403, __( 'Something went wrong.' ) );
  3127. }
  3128. /**
  3129. * Fires after a new comment has been successfully created via XML-RPC.
  3130. *
  3131. * @since 3.4.0
  3132. *
  3133. * @param int $comment_ID ID of the new comment.
  3134. * @param array $args An array of new comment arguments.
  3135. */
  3136. do_action( 'xmlrpc_call_success_wp_newComment', $comment_ID, $args );
  3137. return $comment_ID;
  3138. }
  3139. /**
  3140. * Retrieve all of the comment status.
  3141. *
  3142. * @since 2.7.0
  3143. *
  3144. * @param array $args {
  3145. * Method arguments. Note: arguments must be ordered as documented.
  3146. *
  3147. * @type int $blog_id (unused)
  3148. * @type string $username
  3149. * @type string $password
  3150. * }
  3151. * @return array|IXR_Error
  3152. */
  3153. public function wp_getCommentStatusList( $args ) {
  3154. $this->escape( $args );
  3155. $username = $args[1];
  3156. $password = $args[2];
  3157. if ( ! $user = $this->login( $username, $password ) ) {
  3158. return $this->error;
  3159. }
  3160. if ( ! current_user_can( 'publish_posts' ) ) {
  3161. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
  3162. }
  3163. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3164. do_action( 'xmlrpc_call', 'wp.getCommentStatusList' );
  3165. return get_comment_statuses();
  3166. }
  3167. /**
  3168. * Retrieve comment count.
  3169. *
  3170. * @since 2.5.0
  3171. *
  3172. * @param array $args {
  3173. * Method arguments. Note: arguments must be ordered as documented.
  3174. *
  3175. * @type int $blog_id (unused)
  3176. * @type string $username
  3177. * @type string $password
  3178. * @type int $post_id
  3179. * }
  3180. * @return array|IXR_Error
  3181. */
  3182. public function wp_getCommentCount( $args ) {
  3183. $this->escape( $args );
  3184. $username = $args[1];
  3185. $password = $args[2];
  3186. $post_id = (int) $args[3];
  3187. if ( ! $user = $this->login( $username, $password ) ) {
  3188. return $this->error;
  3189. }
  3190. $post = get_post( $post_id, ARRAY_A );
  3191. if ( empty( $post['ID'] ) ) {
  3192. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3193. }
  3194. if ( ! current_user_can( 'edit_post', $post_id ) ) {
  3195. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details of this post.' ) );
  3196. }
  3197. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3198. do_action( 'xmlrpc_call', 'wp.getCommentCount' );
  3199. $count = wp_count_comments( $post_id );
  3200. return array(
  3201. 'approved' => $count->approved,
  3202. 'awaiting_moderation' => $count->moderated,
  3203. 'spam' => $count->spam,
  3204. 'total_comments' => $count->total_comments
  3205. );
  3206. }
  3207. /**
  3208. * Retrieve post statuses.
  3209. *
  3210. * @since 2.5.0
  3211. *
  3212. * @param array $args {
  3213. * Method arguments. Note: arguments must be ordered as documented.
  3214. *
  3215. * @type int $blog_id (unused)
  3216. * @type string $username
  3217. * @type string $password
  3218. * }
  3219. * @return array|IXR_Error
  3220. */
  3221. public function wp_getPostStatusList( $args ) {
  3222. $this->escape( $args );
  3223. $username = $args[1];
  3224. $password = $args[2];
  3225. if ( !$user = $this->login($username, $password) )
  3226. return $this->error;
  3227. if ( !current_user_can( 'edit_posts' ) )
  3228. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
  3229. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3230. do_action( 'xmlrpc_call', 'wp.getPostStatusList' );
  3231. return get_post_statuses();
  3232. }
  3233. /**
  3234. * Retrieve page statuses.
  3235. *
  3236. * @since 2.5.0
  3237. *
  3238. * @param array $args {
  3239. * Method arguments. Note: arguments must be ordered as documented.
  3240. *
  3241. * @type int $blog_id (unused)
  3242. * @type string $username
  3243. * @type string $password
  3244. * }
  3245. * @return array|IXR_Error
  3246. */
  3247. public function wp_getPageStatusList( $args ) {
  3248. $this->escape( $args );
  3249. $username = $args[1];
  3250. $password = $args[2];
  3251. if ( !$user = $this->login($username, $password) )
  3252. return $this->error;
  3253. if ( !current_user_can( 'edit_pages' ) )
  3254. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
  3255. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3256. do_action( 'xmlrpc_call', 'wp.getPageStatusList' );
  3257. return get_page_statuses();
  3258. }
  3259. /**
  3260. * Retrieve page templates.
  3261. *
  3262. * @since 2.6.0
  3263. *
  3264. * @param array $args {
  3265. * Method arguments. Note: arguments must be ordered as documented.
  3266. *
  3267. * @type int $blog_id (unused)
  3268. * @type string $username
  3269. * @type string $password
  3270. * }
  3271. * @return array|IXR_Error
  3272. */
  3273. public function wp_getPageTemplates( $args ) {
  3274. $this->escape( $args );
  3275. $username = $args[1];
  3276. $password = $args[2];
  3277. if ( !$user = $this->login($username, $password) )
  3278. return $this->error;
  3279. if ( !current_user_can( 'edit_pages' ) )
  3280. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
  3281. $templates = get_page_templates();
  3282. $templates['Default'] = 'default';
  3283. return $templates;
  3284. }
  3285. /**
  3286. * Retrieve blog options.
  3287. *
  3288. * @since 2.6.0
  3289. *
  3290. * @param array $args {
  3291. * Method arguments. Note: arguments must be ordered as documented.
  3292. *
  3293. * @type int $blog_id (unused)
  3294. * @type string $username
  3295. * @type string $password
  3296. * @type array $options
  3297. * }
  3298. * @return array|IXR_Error
  3299. */
  3300. public function wp_getOptions( $args ) {
  3301. $this->escape( $args );
  3302. $username = $args[1];
  3303. $password = $args[2];
  3304. $options = isset( $args[3] ) ? (array) $args[3] : array();
  3305. if ( !$user = $this->login($username, $password) )
  3306. return $this->error;
  3307. // If no specific options where asked for, return all of them
  3308. if ( count( $options ) == 0 )
  3309. $options = array_keys($this->blog_options);
  3310. return $this->_getOptions($options);
  3311. }
  3312. /**
  3313. * Retrieve blog options value from list.
  3314. *
  3315. * @since 2.6.0
  3316. *
  3317. * @param array $options Options to retrieve.
  3318. * @return array
  3319. */
  3320. public function _getOptions($options) {
  3321. $data = array();
  3322. $can_manage = current_user_can( 'manage_options' );
  3323. foreach ( $options as $option ) {
  3324. if ( array_key_exists( $option, $this->blog_options ) ) {
  3325. $data[$option] = $this->blog_options[$option];
  3326. //Is the value static or dynamic?
  3327. if ( isset( $data[$option]['option'] ) ) {
  3328. $data[$option]['value'] = get_option( $data[$option]['option'] );
  3329. unset($data[$option]['option']);
  3330. }
  3331. if ( ! $can_manage )
  3332. $data[$option]['readonly'] = true;
  3333. }
  3334. }
  3335. return $data;
  3336. }
  3337. /**
  3338. * Update blog options.
  3339. *
  3340. * @since 2.6.0
  3341. *
  3342. * @param array $args {
  3343. * Method arguments. Note: arguments must be ordered as documented.
  3344. *
  3345. * @type int $blog_id (unused)
  3346. * @type string $username
  3347. * @type string $password
  3348. * @type array $options
  3349. * }
  3350. * @return array|IXR_Error
  3351. */
  3352. public function wp_setOptions( $args ) {
  3353. $this->escape( $args );
  3354. $username = $args[1];
  3355. $password = $args[2];
  3356. $options = (array) $args[3];
  3357. if ( !$user = $this->login($username, $password) )
  3358. return $this->error;
  3359. if ( !current_user_can( 'manage_options' ) )
  3360. return new IXR_Error( 403, __( 'Sorry, you are not allowed to update options.' ) );
  3361. $option_names = array();
  3362. foreach ( $options as $o_name => $o_value ) {
  3363. $option_names[] = $o_name;
  3364. if ( !array_key_exists( $o_name, $this->blog_options ) )
  3365. continue;
  3366. if ( $this->blog_options[$o_name]['readonly'] == true )
  3367. continue;
  3368. update_option( $this->blog_options[$o_name]['option'], wp_unslash( $o_value ) );
  3369. }
  3370. //Now return the updated values
  3371. return $this->_getOptions($option_names);
  3372. }
  3373. /**
  3374. * Retrieve a media item by ID
  3375. *
  3376. * @since 3.1.0
  3377. *
  3378. * @param array $args {
  3379. * Method arguments. Note: arguments must be ordered as documented.
  3380. *
  3381. * @type int $blog_id (unused)
  3382. * @type string $username
  3383. * @type string $password
  3384. * @type int $attachment_id
  3385. * }
  3386. * @return array|IXR_Error Associative array contains:
  3387. * - 'date_created_gmt'
  3388. * - 'parent'
  3389. * - 'link'
  3390. * - 'thumbnail'
  3391. * - 'title'
  3392. * - 'caption'
  3393. * - 'description'
  3394. * - 'metadata'
  3395. */
  3396. public function wp_getMediaItem( $args ) {
  3397. $this->escape( $args );
  3398. $username = $args[1];
  3399. $password = $args[2];
  3400. $attachment_id = (int) $args[3];
  3401. if ( !$user = $this->login($username, $password) )
  3402. return $this->error;
  3403. if ( !current_user_can( 'upload_files' ) )
  3404. return new IXR_Error( 403, __( 'Sorry, you are not allowed to upload files.' ) );
  3405. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3406. do_action( 'xmlrpc_call', 'wp.getMediaItem' );
  3407. if ( ! $attachment = get_post($attachment_id) )
  3408. return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  3409. return $this->_prepare_media_item( $attachment );
  3410. }
  3411. /**
  3412. * Retrieves a collection of media library items (or attachments)
  3413. *
  3414. * Besides the common blog_id (unused), username, and password arguments, it takes a filter
  3415. * array as last argument.
  3416. *
  3417. * Accepted 'filter' keys are 'parent_id', 'mime_type', 'offset', and 'number'.
  3418. *
  3419. * The defaults are as follows:
  3420. * - 'number' - Default is 5. Total number of media items to retrieve.
  3421. * - 'offset' - Default is 0. See WP_Query::query() for more.
  3422. * - 'parent_id' - Default is ''. The post where the media item is attached. Empty string shows all media items. 0 shows unattached media items.
  3423. * - 'mime_type' - Default is ''. Filter by mime type (e.g., 'image/jpeg', 'application/pdf')
  3424. *
  3425. * @since 3.1.0
  3426. *
  3427. * @param array $args {
  3428. * Method arguments. Note: arguments must be ordered as documented.
  3429. *
  3430. * @type int $blog_id (unused)
  3431. * @type string $username
  3432. * @type string $password
  3433. * @type array $struct
  3434. * }
  3435. * @return array|IXR_Error Contains a collection of media items. See wp_xmlrpc_server::wp_getMediaItem() for a description of each item contents
  3436. */
  3437. public function wp_getMediaLibrary($args) {
  3438. $this->escape($args);
  3439. $username = $args[1];
  3440. $password = $args[2];
  3441. $struct = isset( $args[3] ) ? $args[3] : array() ;
  3442. if ( !$user = $this->login($username, $password) )
  3443. return $this->error;
  3444. if ( !current_user_can( 'upload_files' ) )
  3445. return new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
  3446. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3447. do_action( 'xmlrpc_call', 'wp.getMediaLibrary' );
  3448. $parent_id = ( isset($struct['parent_id']) ) ? absint($struct['parent_id']) : '' ;
  3449. $mime_type = ( isset($struct['mime_type']) ) ? $struct['mime_type'] : '' ;
  3450. $offset = ( isset($struct['offset']) ) ? absint($struct['offset']) : 0 ;
  3451. $number = ( isset($struct['number']) ) ? absint($struct['number']) : -1 ;
  3452. $attachments = get_posts( array('post_type' => 'attachment', 'post_parent' => $parent_id, 'offset' => $offset, 'numberposts' => $number, 'post_mime_type' => $mime_type ) );
  3453. $attachments_struct = array();
  3454. foreach ($attachments as $attachment )
  3455. $attachments_struct[] = $this->_prepare_media_item( $attachment );
  3456. return $attachments_struct;
  3457. }
  3458. /**
  3459. * Retrieves a list of post formats used by the site.
  3460. *
  3461. * @since 3.1.0
  3462. *
  3463. * @param array $args {
  3464. * Method arguments. Note: arguments must be ordered as documented.
  3465. *
  3466. * @type int $blog_id (unused)
  3467. * @type string $username
  3468. * @type string $password
  3469. * }
  3470. * @return array|IXR_Error List of post formats, otherwise IXR_Error object.
  3471. */
  3472. public function wp_getPostFormats( $args ) {
  3473. $this->escape( $args );
  3474. $username = $args[1];
  3475. $password = $args[2];
  3476. if ( !$user = $this->login( $username, $password ) )
  3477. return $this->error;
  3478. if ( !current_user_can( 'edit_posts' ) )
  3479. return new IXR_Error( 403, __( 'Sorry, you are not allowed access to details about this site.' ) );
  3480. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3481. do_action( 'xmlrpc_call', 'wp.getPostFormats' );
  3482. $formats = get_post_format_strings();
  3483. // find out if they want a list of currently supports formats
  3484. if ( isset( $args[3] ) && is_array( $args[3] ) ) {
  3485. if ( $args[3]['show-supported'] ) {
  3486. if ( current_theme_supports( 'post-formats' ) ) {
  3487. $supported = get_theme_support( 'post-formats' );
  3488. $data = array();
  3489. $data['all'] = $formats;
  3490. $data['supported'] = $supported[0];
  3491. $formats = $data;
  3492. }
  3493. }
  3494. }
  3495. return $formats;
  3496. }
  3497. /**
  3498. * Retrieves a post type
  3499. *
  3500. * @since 3.4.0
  3501. *
  3502. * @see get_post_type_object()
  3503. *
  3504. * @param array $args {
  3505. * Method arguments. Note: arguments must be ordered as documented.
  3506. *
  3507. * @type int $blog_id (unused)
  3508. * @type string $username
  3509. * @type string $password
  3510. * @type string $post_type_name
  3511. * @type array $fields (optional)
  3512. * }
  3513. * @return array|IXR_Error Array contains:
  3514. * - 'labels'
  3515. * - 'description'
  3516. * - 'capability_type'
  3517. * - 'cap'
  3518. * - 'map_meta_cap'
  3519. * - 'hierarchical'
  3520. * - 'menu_position'
  3521. * - 'taxonomies'
  3522. * - 'supports'
  3523. */
  3524. public function wp_getPostType( $args ) {
  3525. if ( ! $this->minimum_args( $args, 4 ) )
  3526. return $this->error;
  3527. $this->escape( $args );
  3528. $username = $args[1];
  3529. $password = $args[2];
  3530. $post_type_name = $args[3];
  3531. if ( isset( $args[4] ) ) {
  3532. $fields = $args[4];
  3533. } else {
  3534. /**
  3535. * Filters the default query fields used by the given XML-RPC method.
  3536. *
  3537. * @since 3.4.0
  3538. *
  3539. * @param array $fields An array of post type query fields for the given method.
  3540. * @param string $method The method name.
  3541. */
  3542. $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostType' );
  3543. }
  3544. if ( !$user = $this->login( $username, $password ) )
  3545. return $this->error;
  3546. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3547. do_action( 'xmlrpc_call', 'wp.getPostType' );
  3548. if ( ! post_type_exists( $post_type_name ) )
  3549. return new IXR_Error( 403, __( 'Invalid post type.' ) );
  3550. $post_type = get_post_type_object( $post_type_name );
  3551. if ( ! current_user_can( $post_type->cap->edit_posts ) )
  3552. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type.' ) );
  3553. return $this->_prepare_post_type( $post_type, $fields );
  3554. }
  3555. /**
  3556. * Retrieves a post types
  3557. *
  3558. * @since 3.4.0
  3559. *
  3560. * @see get_post_types()
  3561. *
  3562. * @param array $args {
  3563. * Method arguments. Note: arguments must be ordered as documented.
  3564. *
  3565. * @type int $blog_id (unused)
  3566. * @type string $username
  3567. * @type string $password
  3568. * @type array $filter (optional)
  3569. * @type array $fields (optional)
  3570. * }
  3571. * @return array|IXR_Error
  3572. */
  3573. public function wp_getPostTypes( $args ) {
  3574. if ( ! $this->minimum_args( $args, 3 ) )
  3575. return $this->error;
  3576. $this->escape( $args );
  3577. $username = $args[1];
  3578. $password = $args[2];
  3579. $filter = isset( $args[3] ) ? $args[3] : array( 'public' => true );
  3580. if ( isset( $args[4] ) ) {
  3581. $fields = $args[4];
  3582. } else {
  3583. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3584. $fields = apply_filters( 'xmlrpc_default_posttype_fields', array( 'labels', 'cap', 'taxonomies' ), 'wp.getPostTypes' );
  3585. }
  3586. if ( ! $user = $this->login( $username, $password ) )
  3587. return $this->error;
  3588. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3589. do_action( 'xmlrpc_call', 'wp.getPostTypes' );
  3590. $post_types = get_post_types( $filter, 'objects' );
  3591. $struct = array();
  3592. foreach ( $post_types as $post_type ) {
  3593. if ( ! current_user_can( $post_type->cap->edit_posts ) )
  3594. continue;
  3595. $struct[$post_type->name] = $this->_prepare_post_type( $post_type, $fields );
  3596. }
  3597. return $struct;
  3598. }
  3599. /**
  3600. * Retrieve revisions for a specific post.
  3601. *
  3602. * @since 3.5.0
  3603. *
  3604. * The optional $fields parameter specifies what fields will be included
  3605. * in the response array.
  3606. *
  3607. * @uses wp_get_post_revisions()
  3608. * @see wp_getPost() for more on $fields
  3609. *
  3610. * @param array $args {
  3611. * Method arguments. Note: arguments must be ordered as documented.
  3612. *
  3613. * @type int $blog_id (unused)
  3614. * @type string $username
  3615. * @type string $password
  3616. * @type int $post_id
  3617. * @type array $fields (optional)
  3618. * }
  3619. * @return array|IXR_Error contains a collection of posts.
  3620. */
  3621. public function wp_getRevisions( $args ) {
  3622. if ( ! $this->minimum_args( $args, 4 ) )
  3623. return $this->error;
  3624. $this->escape( $args );
  3625. $username = $args[1];
  3626. $password = $args[2];
  3627. $post_id = (int) $args[3];
  3628. if ( isset( $args[4] ) ) {
  3629. $fields = $args[4];
  3630. } else {
  3631. /**
  3632. * Filters the default revision query fields used by the given XML-RPC method.
  3633. *
  3634. * @since 3.5.0
  3635. *
  3636. * @param array $field An array of revision query fields.
  3637. * @param string $method The method name.
  3638. */
  3639. $fields = apply_filters( 'xmlrpc_default_revision_fields', array( 'post_date', 'post_date_gmt' ), 'wp.getRevisions' );
  3640. }
  3641. if ( ! $user = $this->login( $username, $password ) )
  3642. return $this->error;
  3643. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3644. do_action( 'xmlrpc_call', 'wp.getRevisions' );
  3645. if ( ! $post = get_post( $post_id ) )
  3646. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3647. if ( ! current_user_can( 'edit_post', $post_id ) )
  3648. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
  3649. // Check if revisions are enabled.
  3650. if ( ! wp_revisions_enabled( $post ) )
  3651. return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
  3652. $revisions = wp_get_post_revisions( $post_id );
  3653. if ( ! $revisions )
  3654. return array();
  3655. $struct = array();
  3656. foreach ( $revisions as $revision ) {
  3657. if ( ! current_user_can( 'read_post', $revision->ID ) )
  3658. continue;
  3659. // Skip autosaves
  3660. if ( wp_is_post_autosave( $revision ) )
  3661. continue;
  3662. $struct[] = $this->_prepare_post( get_object_vars( $revision ), $fields );
  3663. }
  3664. return $struct;
  3665. }
  3666. /**
  3667. * Restore a post revision
  3668. *
  3669. * @since 3.5.0
  3670. *
  3671. * @uses wp_restore_post_revision()
  3672. *
  3673. * @param array $args {
  3674. * Method arguments. Note: arguments must be ordered as documented.
  3675. *
  3676. * @type int $blog_id (unused)
  3677. * @type string $username
  3678. * @type string $password
  3679. * @type int $revision_id
  3680. * }
  3681. * @return bool|IXR_Error false if there was an error restoring, true if success.
  3682. */
  3683. public function wp_restoreRevision( $args ) {
  3684. if ( ! $this->minimum_args( $args, 3 ) )
  3685. return $this->error;
  3686. $this->escape( $args );
  3687. $username = $args[1];
  3688. $password = $args[2];
  3689. $revision_id = (int) $args[3];
  3690. if ( ! $user = $this->login( $username, $password ) )
  3691. return $this->error;
  3692. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3693. do_action( 'xmlrpc_call', 'wp.restoreRevision' );
  3694. if ( ! $revision = wp_get_post_revision( $revision_id ) )
  3695. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3696. if ( wp_is_post_autosave( $revision ) )
  3697. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3698. if ( ! $post = get_post( $revision->post_parent ) )
  3699. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3700. if ( ! current_user_can( 'edit_post', $revision->post_parent ) )
  3701. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  3702. // Check if revisions are disabled.
  3703. if ( ! wp_revisions_enabled( $post ) )
  3704. return new IXR_Error( 401, __( 'Sorry, revisions are disabled.' ) );
  3705. $post = wp_restore_post_revision( $revision_id );
  3706. return (bool) $post;
  3707. }
  3708. /* Blogger API functions.
  3709. * specs on http://plant.blogger.com/api and https://groups.yahoo.com/group/bloggerDev/
  3710. */
  3711. /**
  3712. * Retrieve blogs that user owns.
  3713. *
  3714. * Will make more sense once we support multiple blogs.
  3715. *
  3716. * @since 1.5.0
  3717. *
  3718. * @param array $args {
  3719. * Method arguments. Note: arguments must be ordered as documented.
  3720. *
  3721. * @type int $blog_id (unused)
  3722. * @type string $username
  3723. * @type string $password
  3724. * }
  3725. * @return array|IXR_Error
  3726. */
  3727. public function blogger_getUsersBlogs($args) {
  3728. if ( ! $this->minimum_args( $args, 3 ) ) {
  3729. return $this->error;
  3730. }
  3731. if ( is_multisite() ) {
  3732. return $this->_multisite_getUsersBlogs($args);
  3733. }
  3734. $this->escape($args);
  3735. $username = $args[1];
  3736. $password = $args[2];
  3737. if ( !$user = $this->login($username, $password) )
  3738. return $this->error;
  3739. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3740. do_action( 'xmlrpc_call', 'blogger.getUsersBlogs' );
  3741. $is_admin = current_user_can('manage_options');
  3742. $struct = array(
  3743. 'isAdmin' => $is_admin,
  3744. 'url' => get_option('home') . '/',
  3745. 'blogid' => '1',
  3746. 'blogName' => get_option('blogname'),
  3747. 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ),
  3748. );
  3749. return array($struct);
  3750. }
  3751. /**
  3752. * Private function for retrieving a users blogs for multisite setups
  3753. *
  3754. * @since 3.0.0
  3755. *
  3756. * @param array $args {
  3757. * Method arguments. Note: arguments must be ordered as documented.
  3758. *
  3759. * @type string $username Username.
  3760. * @type string $password Password.
  3761. * }
  3762. * @return array|IXR_Error
  3763. */
  3764. protected function _multisite_getUsersBlogs( $args ) {
  3765. $current_blog = get_site();
  3766. $domain = $current_blog->domain;
  3767. $path = $current_blog->path . 'xmlrpc.php';
  3768. $rpc = new IXR_Client( set_url_scheme( "http://{$domain}{$path}" ) );
  3769. $rpc->query('wp.getUsersBlogs', $args[1], $args[2]);
  3770. $blogs = $rpc->getResponse();
  3771. if ( isset($blogs['faultCode']) )
  3772. return new IXR_Error($blogs['faultCode'], $blogs['faultString']);
  3773. if ( $_SERVER['HTTP_HOST'] == $domain && $_SERVER['REQUEST_URI'] == $path ) {
  3774. return $blogs;
  3775. } else {
  3776. foreach ( (array) $blogs as $blog ) {
  3777. if ( strpos($blog['url'], $_SERVER['HTTP_HOST']) )
  3778. return array($blog);
  3779. }
  3780. return array();
  3781. }
  3782. }
  3783. /**
  3784. * Retrieve user's data.
  3785. *
  3786. * Gives your client some info about you, so you don't have to.
  3787. *
  3788. * @since 1.5.0
  3789. *
  3790. * @param array $args {
  3791. * Method arguments. Note: arguments must be ordered as documented.
  3792. *
  3793. * @type int $blog_id (unused)
  3794. * @type string $username
  3795. * @type string $password
  3796. * }
  3797. * @return array|IXR_Error
  3798. */
  3799. public function blogger_getUserInfo( $args ) {
  3800. $this->escape( $args );
  3801. $username = $args[1];
  3802. $password = $args[2];
  3803. if ( !$user = $this->login($username, $password) )
  3804. return $this->error;
  3805. if ( !current_user_can( 'edit_posts' ) )
  3806. return new IXR_Error( 401, __( 'Sorry, you are not allowed to access user data on this site.' ) );
  3807. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3808. do_action( 'xmlrpc_call', 'blogger.getUserInfo' );
  3809. $struct = array(
  3810. 'nickname' => $user->nickname,
  3811. 'userid' => $user->ID,
  3812. 'url' => $user->user_url,
  3813. 'lastname' => $user->last_name,
  3814. 'firstname' => $user->first_name
  3815. );
  3816. return $struct;
  3817. }
  3818. /**
  3819. * Retrieve post.
  3820. *
  3821. * @since 1.5.0
  3822. *
  3823. * @param array $args {
  3824. * Method arguments. Note: arguments must be ordered as documented.
  3825. *
  3826. * @type int $blog_id (unused)
  3827. * @type int $post_ID
  3828. * @type string $username
  3829. * @type string $password
  3830. * }
  3831. * @return array|IXR_Error
  3832. */
  3833. public function blogger_getPost( $args ) {
  3834. $this->escape( $args );
  3835. $post_ID = (int) $args[1];
  3836. $username = $args[2];
  3837. $password = $args[3];
  3838. if ( !$user = $this->login($username, $password) )
  3839. return $this->error;
  3840. $post_data = get_post($post_ID, ARRAY_A);
  3841. if ( ! $post_data )
  3842. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  3843. if ( !current_user_can( 'edit_post', $post_ID ) )
  3844. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  3845. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3846. do_action( 'xmlrpc_call', 'blogger.getPost' );
  3847. $categories = implode(',', wp_get_post_categories($post_ID));
  3848. $content = '<title>'.wp_unslash($post_data['post_title']).'</title>';
  3849. $content .= '<category>'.$categories.'</category>';
  3850. $content .= wp_unslash($post_data['post_content']);
  3851. $struct = array(
  3852. 'userid' => $post_data['post_author'],
  3853. 'dateCreated' => $this->_convert_date( $post_data['post_date'] ),
  3854. 'content' => $content,
  3855. 'postid' => (string) $post_data['ID']
  3856. );
  3857. return $struct;
  3858. }
  3859. /**
  3860. * Retrieve list of recent posts.
  3861. *
  3862. * @since 1.5.0
  3863. *
  3864. * @param array $args {
  3865. * Method arguments. Note: arguments must be ordered as documented.
  3866. *
  3867. * @type string $appkey (unused)
  3868. * @type int $blog_id (unused)
  3869. * @type string $username
  3870. * @type string $password
  3871. * @type int $numberposts (optional)
  3872. * }
  3873. * @return array|IXR_Error
  3874. */
  3875. public function blogger_getRecentPosts( $args ) {
  3876. $this->escape($args);
  3877. // $args[0] = appkey - ignored
  3878. $username = $args[2];
  3879. $password = $args[3];
  3880. if ( isset( $args[4] ) )
  3881. $query = array( 'numberposts' => absint( $args[4] ) );
  3882. else
  3883. $query = array();
  3884. if ( !$user = $this->login($username, $password) )
  3885. return $this->error;
  3886. if ( ! current_user_can( 'edit_posts' ) )
  3887. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
  3888. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3889. do_action( 'xmlrpc_call', 'blogger.getRecentPosts' );
  3890. $posts_list = wp_get_recent_posts( $query );
  3891. if ( !$posts_list ) {
  3892. $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
  3893. return $this->error;
  3894. }
  3895. $recent_posts = array();
  3896. foreach ($posts_list as $entry) {
  3897. if ( !current_user_can( 'edit_post', $entry['ID'] ) )
  3898. continue;
  3899. $post_date = $this->_convert_date( $entry['post_date'] );
  3900. $categories = implode(',', wp_get_post_categories($entry['ID']));
  3901. $content = '<title>'.wp_unslash($entry['post_title']).'</title>';
  3902. $content .= '<category>'.$categories.'</category>';
  3903. $content .= wp_unslash($entry['post_content']);
  3904. $recent_posts[] = array(
  3905. 'userid' => $entry['post_author'],
  3906. 'dateCreated' => $post_date,
  3907. 'content' => $content,
  3908. 'postid' => (string) $entry['ID'],
  3909. );
  3910. }
  3911. return $recent_posts;
  3912. }
  3913. /**
  3914. * Deprecated.
  3915. *
  3916. * @since 1.5.0
  3917. * @deprecated 3.5.0
  3918. *
  3919. * @param array $args Unused.
  3920. * @return IXR_Error Error object.
  3921. */
  3922. public function blogger_getTemplate($args) {
  3923. return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
  3924. }
  3925. /**
  3926. * Deprecated.
  3927. *
  3928. * @since 1.5.0
  3929. * @deprecated 3.5.0
  3930. *
  3931. * @param array $args Unused.
  3932. * @return IXR_Error Error object.
  3933. */
  3934. public function blogger_setTemplate($args) {
  3935. return new IXR_Error( 403, __('Sorry, that file cannot be edited.' ) );
  3936. }
  3937. /**
  3938. * Creates new post.
  3939. *
  3940. * @since 1.5.0
  3941. *
  3942. * @param array $args {
  3943. * Method arguments. Note: arguments must be ordered as documented.
  3944. *
  3945. * @type string $appkey (unused)
  3946. * @type int $blog_id (unused)
  3947. * @type string $username
  3948. * @type string $password
  3949. * @type string $content
  3950. * @type string $publish
  3951. * }
  3952. * @return int|IXR_Error
  3953. */
  3954. public function blogger_newPost( $args ) {
  3955. $this->escape( $args );
  3956. $username = $args[2];
  3957. $password = $args[3];
  3958. $content = $args[4];
  3959. $publish = $args[5];
  3960. if ( !$user = $this->login($username, $password) )
  3961. return $this->error;
  3962. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  3963. do_action( 'xmlrpc_call', 'blogger.newPost' );
  3964. $cap = ($publish) ? 'publish_posts' : 'edit_posts';
  3965. if ( ! current_user_can( get_post_type_object( 'post' )->cap->create_posts ) || !current_user_can($cap) )
  3966. return new IXR_Error(401, __('Sorry, you are not allowed to post on this site.'));
  3967. $post_status = ($publish) ? 'publish' : 'draft';
  3968. $post_author = $user->ID;
  3969. $post_title = xmlrpc_getposttitle($content);
  3970. $post_category = xmlrpc_getpostcategory($content);
  3971. $post_content = xmlrpc_removepostdata($content);
  3972. $post_date = current_time('mysql');
  3973. $post_date_gmt = current_time('mysql', 1);
  3974. $post_data = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
  3975. $post_ID = wp_insert_post($post_data);
  3976. if ( is_wp_error( $post_ID ) )
  3977. return new IXR_Error(500, $post_ID->get_error_message());
  3978. if ( !$post_ID )
  3979. return new IXR_Error(500, __('Sorry, your entry could not be posted.'));
  3980. $this->attach_uploads( $post_ID, $post_content );
  3981. /**
  3982. * Fires after a new post has been successfully created via the XML-RPC Blogger API.
  3983. *
  3984. * @since 3.4.0
  3985. *
  3986. * @param int $post_ID ID of the new post.
  3987. * @param array $args An array of new post arguments.
  3988. */
  3989. do_action( 'xmlrpc_call_success_blogger_newPost', $post_ID, $args );
  3990. return $post_ID;
  3991. }
  3992. /**
  3993. * Edit a post.
  3994. *
  3995. * @since 1.5.0
  3996. *
  3997. * @param array $args {
  3998. * Method arguments. Note: arguments must be ordered as documented.
  3999. *
  4000. * @type int $blog_id (unused)
  4001. * @type int $post_ID
  4002. * @type string $username
  4003. * @type string $password
  4004. * @type string $content
  4005. * @type bool $publish
  4006. * }
  4007. * @return true|IXR_Error true when done.
  4008. */
  4009. public function blogger_editPost( $args ) {
  4010. $this->escape($args);
  4011. $post_ID = (int) $args[1];
  4012. $username = $args[2];
  4013. $password = $args[3];
  4014. $content = $args[4];
  4015. $publish = $args[5];
  4016. if ( ! $user = $this->login( $username, $password ) ) {
  4017. return $this->error;
  4018. }
  4019. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4020. do_action( 'xmlrpc_call', 'blogger.editPost' );
  4021. $actual_post = get_post( $post_ID, ARRAY_A );
  4022. if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
  4023. return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
  4024. }
  4025. $this->escape($actual_post);
  4026. if ( ! current_user_can( 'edit_post', $post_ID ) ) {
  4027. return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
  4028. }
  4029. if ( 'publish' == $actual_post['post_status'] && ! current_user_can( 'publish_posts' ) ) {
  4030. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
  4031. }
  4032. $postdata = array();
  4033. $postdata['ID'] = $actual_post['ID'];
  4034. $postdata['post_content'] = xmlrpc_removepostdata( $content );
  4035. $postdata['post_title'] = xmlrpc_getposttitle( $content );
  4036. $postdata['post_category'] = xmlrpc_getpostcategory( $content );
  4037. $postdata['post_status'] = $actual_post['post_status'];
  4038. $postdata['post_excerpt'] = $actual_post['post_excerpt'];
  4039. $postdata['post_status'] = $publish ? 'publish' : 'draft';
  4040. $result = wp_update_post( $postdata );
  4041. if ( ! $result ) {
  4042. return new IXR_Error(500, __('For some strange yet very annoying reason, this post could not be edited.'));
  4043. }
  4044. $this->attach_uploads( $actual_post['ID'], $postdata['post_content'] );
  4045. /**
  4046. * Fires after a post has been successfully updated via the XML-RPC Blogger API.
  4047. *
  4048. * @since 3.4.0
  4049. *
  4050. * @param int $post_ID ID of the updated post.
  4051. * @param array $args An array of arguments for the post to edit.
  4052. */
  4053. do_action( 'xmlrpc_call_success_blogger_editPost', $post_ID, $args );
  4054. return true;
  4055. }
  4056. /**
  4057. * Remove a post.
  4058. *
  4059. * @since 1.5.0
  4060. *
  4061. * @param array $args {
  4062. * Method arguments. Note: arguments must be ordered as documented.
  4063. *
  4064. * @type int $blog_id (unused)
  4065. * @type int $post_ID
  4066. * @type string $username
  4067. * @type string $password
  4068. * }
  4069. * @return true|IXR_Error True when post is deleted.
  4070. */
  4071. public function blogger_deletePost( $args ) {
  4072. $this->escape( $args );
  4073. $post_ID = (int) $args[1];
  4074. $username = $args[2];
  4075. $password = $args[3];
  4076. if ( !$user = $this->login($username, $password) )
  4077. return $this->error;
  4078. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4079. do_action( 'xmlrpc_call', 'blogger.deletePost' );
  4080. $actual_post = get_post( $post_ID, ARRAY_A );
  4081. if ( ! $actual_post || $actual_post['post_type'] != 'post' ) {
  4082. return new IXR_Error( 404, __( 'Sorry, no such post.' ) );
  4083. }
  4084. if ( ! current_user_can( 'delete_post', $post_ID ) ) {
  4085. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
  4086. }
  4087. $result = wp_delete_post( $post_ID );
  4088. if ( ! $result ) {
  4089. return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
  4090. }
  4091. /**
  4092. * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
  4093. *
  4094. * @since 3.4.0
  4095. *
  4096. * @param int $post_ID ID of the deleted post.
  4097. * @param array $args An array of arguments to delete the post.
  4098. */
  4099. do_action( 'xmlrpc_call_success_blogger_deletePost', $post_ID, $args );
  4100. return true;
  4101. }
  4102. /* MetaWeblog API functions
  4103. * specs on wherever Dave Winer wants them to be
  4104. */
  4105. /**
  4106. * Create a new post.
  4107. *
  4108. * The 'content_struct' argument must contain:
  4109. * - title
  4110. * - description
  4111. * - mt_excerpt
  4112. * - mt_text_more
  4113. * - mt_keywords
  4114. * - mt_tb_ping_urls
  4115. * - categories
  4116. *
  4117. * Also, it can optionally contain:
  4118. * - wp_slug
  4119. * - wp_password
  4120. * - wp_page_parent_id
  4121. * - wp_page_order
  4122. * - wp_author_id
  4123. * - post_status | page_status - can be 'draft', 'private', 'publish', or 'pending'
  4124. * - mt_allow_comments - can be 'open' or 'closed'
  4125. * - mt_allow_pings - can be 'open' or 'closed'
  4126. * - date_created_gmt
  4127. * - dateCreated
  4128. * - wp_post_thumbnail
  4129. *
  4130. * @since 1.5.0
  4131. *
  4132. * @param array $args {
  4133. * Method arguments. Note: arguments must be ordered as documented.
  4134. *
  4135. * @type int $blog_id (unused)
  4136. * @type string $username
  4137. * @type string $password
  4138. * @type array $content_struct
  4139. * @type int $publish
  4140. * }
  4141. * @return int|IXR_Error
  4142. */
  4143. public function mw_newPost($args) {
  4144. $this->escape($args);
  4145. $username = $args[1];
  4146. $password = $args[2];
  4147. $content_struct = $args[3];
  4148. $publish = isset( $args[4] ) ? $args[4] : 0;
  4149. if ( !$user = $this->login($username, $password) )
  4150. return $this->error;
  4151. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4152. do_action( 'xmlrpc_call', 'metaWeblog.newPost' );
  4153. $page_template = '';
  4154. if ( !empty( $content_struct['post_type'] ) ) {
  4155. if ( $content_struct['post_type'] == 'page' ) {
  4156. if ( $publish )
  4157. $cap = 'publish_pages';
  4158. elseif ( isset( $content_struct['page_status'] ) && 'publish' == $content_struct['page_status'] )
  4159. $cap = 'publish_pages';
  4160. else
  4161. $cap = 'edit_pages';
  4162. $error_message = __( 'Sorry, you are not allowed to publish pages on this site.' );
  4163. $post_type = 'page';
  4164. if ( !empty( $content_struct['wp_page_template'] ) )
  4165. $page_template = $content_struct['wp_page_template'];
  4166. } elseif ( $content_struct['post_type'] == 'post' ) {
  4167. if ( $publish )
  4168. $cap = 'publish_posts';
  4169. elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'] )
  4170. $cap = 'publish_posts';
  4171. else
  4172. $cap = 'edit_posts';
  4173. $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
  4174. $post_type = 'post';
  4175. } else {
  4176. // No other post_type values are allowed here
  4177. return new IXR_Error( 401, __( 'Invalid post type.' ) );
  4178. }
  4179. } else {
  4180. if ( $publish )
  4181. $cap = 'publish_posts';
  4182. elseif ( isset( $content_struct['post_status'] ) && 'publish' == $content_struct['post_status'])
  4183. $cap = 'publish_posts';
  4184. else
  4185. $cap = 'edit_posts';
  4186. $error_message = __( 'Sorry, you are not allowed to publish posts on this site.' );
  4187. $post_type = 'post';
  4188. }
  4189. if ( ! current_user_can( get_post_type_object( $post_type )->cap->create_posts ) )
  4190. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts on this site.' ) );
  4191. if ( !current_user_can( $cap ) )
  4192. return new IXR_Error( 401, $error_message );
  4193. // Check for a valid post format if one was given
  4194. if ( isset( $content_struct['wp_post_format'] ) ) {
  4195. $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
  4196. if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
  4197. return new IXR_Error( 404, __( 'Invalid post format.' ) );
  4198. }
  4199. }
  4200. // Let WordPress generate the post_name (slug) unless
  4201. // one has been provided.
  4202. $post_name = "";
  4203. if ( isset($content_struct['wp_slug']) )
  4204. $post_name = $content_struct['wp_slug'];
  4205. // Only use a password if one was given.
  4206. if ( isset($content_struct['wp_password']) )
  4207. $post_password = $content_struct['wp_password'];
  4208. // Only set a post parent if one was provided.
  4209. if ( isset($content_struct['wp_page_parent_id']) )
  4210. $post_parent = $content_struct['wp_page_parent_id'];
  4211. // Only set the menu_order if it was provided.
  4212. if ( isset($content_struct['wp_page_order']) )
  4213. $menu_order = $content_struct['wp_page_order'];
  4214. $post_author = $user->ID;
  4215. // If an author id was provided then use it instead.
  4216. if ( isset( $content_struct['wp_author_id'] ) && ( $user->ID != $content_struct['wp_author_id'] ) ) {
  4217. switch ( $post_type ) {
  4218. case "post":
  4219. if ( !current_user_can( 'edit_others_posts' ) )
  4220. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create posts as this user.' ) );
  4221. break;
  4222. case "page":
  4223. if ( !current_user_can( 'edit_others_pages' ) )
  4224. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create pages as this user.' ) );
  4225. break;
  4226. default:
  4227. return new IXR_Error( 401, __( 'Invalid post type.' ) );
  4228. }
  4229. $author = get_userdata( $content_struct['wp_author_id'] );
  4230. if ( ! $author )
  4231. return new IXR_Error( 404, __( 'Invalid author ID.' ) );
  4232. $post_author = $content_struct['wp_author_id'];
  4233. }
  4234. $post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
  4235. $post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;
  4236. $post_status = $publish ? 'publish' : 'draft';
  4237. if ( isset( $content_struct["{$post_type}_status"] ) ) {
  4238. switch ( $content_struct["{$post_type}_status"] ) {
  4239. case 'draft':
  4240. case 'pending':
  4241. case 'private':
  4242. case 'publish':
  4243. $post_status = $content_struct["{$post_type}_status"];
  4244. break;
  4245. default:
  4246. $post_status = $publish ? 'publish' : 'draft';
  4247. break;
  4248. }
  4249. }
  4250. $post_excerpt = isset($content_struct['mt_excerpt']) ? $content_struct['mt_excerpt'] : null;
  4251. $post_more = isset($content_struct['mt_text_more']) ? $content_struct['mt_text_more'] : null;
  4252. $tags_input = isset($content_struct['mt_keywords']) ? $content_struct['mt_keywords'] : null;
  4253. if ( isset($content_struct['mt_allow_comments']) ) {
  4254. if ( !is_numeric($content_struct['mt_allow_comments']) ) {
  4255. switch ( $content_struct['mt_allow_comments'] ) {
  4256. case 'closed':
  4257. $comment_status = 'closed';
  4258. break;
  4259. case 'open':
  4260. $comment_status = 'open';
  4261. break;
  4262. default:
  4263. $comment_status = get_default_comment_status( $post_type );
  4264. break;
  4265. }
  4266. } else {
  4267. switch ( (int) $content_struct['mt_allow_comments'] ) {
  4268. case 0:
  4269. case 2:
  4270. $comment_status = 'closed';
  4271. break;
  4272. case 1:
  4273. $comment_status = 'open';
  4274. break;
  4275. default:
  4276. $comment_status = get_default_comment_status( $post_type );
  4277. break;
  4278. }
  4279. }
  4280. } else {
  4281. $comment_status = get_default_comment_status( $post_type );
  4282. }
  4283. if ( isset($content_struct['mt_allow_pings']) ) {
  4284. if ( !is_numeric($content_struct['mt_allow_pings']) ) {
  4285. switch ( $content_struct['mt_allow_pings'] ) {
  4286. case 'closed':
  4287. $ping_status = 'closed';
  4288. break;
  4289. case 'open':
  4290. $ping_status = 'open';
  4291. break;
  4292. default:
  4293. $ping_status = get_default_comment_status( $post_type, 'pingback' );
  4294. break;
  4295. }
  4296. } else {
  4297. switch ( (int) $content_struct['mt_allow_pings'] ) {
  4298. case 0:
  4299. $ping_status = 'closed';
  4300. break;
  4301. case 1:
  4302. $ping_status = 'open';
  4303. break;
  4304. default:
  4305. $ping_status = get_default_comment_status( $post_type, 'pingback' );
  4306. break;
  4307. }
  4308. }
  4309. } else {
  4310. $ping_status = get_default_comment_status( $post_type, 'pingback' );
  4311. }
  4312. if ( $post_more )
  4313. $post_content = $post_content . '<!--more-->' . $post_more;
  4314. $to_ping = null;
  4315. if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
  4316. $to_ping = $content_struct['mt_tb_ping_urls'];
  4317. if ( is_array($to_ping) )
  4318. $to_ping = implode(' ', $to_ping);
  4319. }
  4320. // Do some timestamp voodoo
  4321. if ( !empty( $content_struct['date_created_gmt'] ) )
  4322. // We know this is supposed to be GMT, so we're going to slap that Z on there by force
  4323. $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
  4324. elseif ( !empty( $content_struct['dateCreated']) )
  4325. $dateCreated = $content_struct['dateCreated']->getIso();
  4326. if ( !empty( $dateCreated ) ) {
  4327. $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
  4328. $post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
  4329. } else {
  4330. $post_date = '';
  4331. $post_date_gmt = '';
  4332. }
  4333. $post_category = array();
  4334. if ( isset( $content_struct['categories'] ) ) {
  4335. $catnames = $content_struct['categories'];
  4336. if ( is_array($catnames) ) {
  4337. foreach ($catnames as $cat) {
  4338. $post_category[] = get_cat_ID($cat);
  4339. }
  4340. }
  4341. }
  4342. $postdata = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'to_ping', 'post_type', 'post_name', 'post_password', 'post_parent', 'menu_order', 'tags_input', 'page_template');
  4343. $post_ID = $postdata['ID'] = get_default_post_to_edit( $post_type, true )->ID;
  4344. // Only posts can be sticky
  4345. if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
  4346. $data = $postdata;
  4347. $data['sticky'] = $content_struct['sticky'];
  4348. $error = $this->_toggle_sticky( $data );
  4349. if ( $error ) {
  4350. return $error;
  4351. }
  4352. }
  4353. if ( isset($content_struct['custom_fields']) )
  4354. $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
  4355. if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
  4356. if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
  4357. return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  4358. unset( $content_struct['wp_post_thumbnail'] );
  4359. }
  4360. // Handle enclosures
  4361. $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
  4362. $this->add_enclosure_if_new($post_ID, $thisEnclosure);
  4363. $this->attach_uploads( $post_ID, $post_content );
  4364. // Handle post formats if assigned, value is validated earlier
  4365. // in this function
  4366. if ( isset( $content_struct['wp_post_format'] ) )
  4367. set_post_format( $post_ID, $content_struct['wp_post_format'] );
  4368. $post_ID = wp_insert_post( $postdata, true );
  4369. if ( is_wp_error( $post_ID ) )
  4370. return new IXR_Error(500, $post_ID->get_error_message());
  4371. if ( !$post_ID )
  4372. return new IXR_Error(500, __('Sorry, your entry could not be posted.'));
  4373. /**
  4374. * Fires after a new post has been successfully created via the XML-RPC MovableType API.
  4375. *
  4376. * @since 3.4.0
  4377. *
  4378. * @param int $post_ID ID of the new post.
  4379. * @param array $args An array of arguments to create the new post.
  4380. */
  4381. do_action( 'xmlrpc_call_success_mw_newPost', $post_ID, $args );
  4382. return strval($post_ID);
  4383. }
  4384. /**
  4385. * Adds an enclosure to a post if it's new.
  4386. *
  4387. * @since 2.8.0
  4388. *
  4389. * @param integer $post_ID Post ID.
  4390. * @param array $enclosure Enclosure data.
  4391. */
  4392. public function add_enclosure_if_new( $post_ID, $enclosure ) {
  4393. if ( is_array( $enclosure ) && isset( $enclosure['url'] ) && isset( $enclosure['length'] ) && isset( $enclosure['type'] ) ) {
  4394. $encstring = $enclosure['url'] . "\n" . $enclosure['length'] . "\n" . $enclosure['type'] . "\n";
  4395. $found = false;
  4396. if ( $enclosures = get_post_meta( $post_ID, 'enclosure' ) ) {
  4397. foreach ( $enclosures as $enc ) {
  4398. // This method used to omit the trailing new line. #23219
  4399. if ( rtrim( $enc, "\n" ) == rtrim( $encstring, "\n" ) ) {
  4400. $found = true;
  4401. break;
  4402. }
  4403. }
  4404. }
  4405. if ( ! $found )
  4406. add_post_meta( $post_ID, 'enclosure', $encstring );
  4407. }
  4408. }
  4409. /**
  4410. * Attach upload to a post.
  4411. *
  4412. * @since 2.1.0
  4413. *
  4414. * @global wpdb $wpdb WordPress database abstraction object.
  4415. *
  4416. * @param int $post_ID Post ID.
  4417. * @param string $post_content Post Content for attachment.
  4418. */
  4419. public function attach_uploads( $post_ID, $post_content ) {
  4420. global $wpdb;
  4421. // find any unattached files
  4422. $attachments = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts} WHERE post_parent = '0' AND post_type = 'attachment'" );
  4423. if ( is_array( $attachments ) ) {
  4424. foreach ( $attachments as $file ) {
  4425. if ( ! empty( $file->guid ) && strpos( $post_content, $file->guid ) !== false )
  4426. $wpdb->update($wpdb->posts, array('post_parent' => $post_ID), array('ID' => $file->ID) );
  4427. }
  4428. }
  4429. }
  4430. /**
  4431. * Edit a post.
  4432. *
  4433. * @since 1.5.0
  4434. *
  4435. * @param array $args {
  4436. * Method arguments. Note: arguments must be ordered as documented.
  4437. *
  4438. * @type int $blog_id (unused)
  4439. * @type string $username
  4440. * @type string $password
  4441. * @type array $content_struct
  4442. * @type int $publish
  4443. * }
  4444. * @return bool|IXR_Error True on success.
  4445. */
  4446. public function mw_editPost( $args ) {
  4447. $this->escape( $args );
  4448. $post_ID = (int) $args[0];
  4449. $username = $args[1];
  4450. $password = $args[2];
  4451. $content_struct = $args[3];
  4452. $publish = isset( $args[4] ) ? $args[4] : 0;
  4453. if ( ! $user = $this->login($username, $password) )
  4454. return $this->error;
  4455. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4456. do_action( 'xmlrpc_call', 'metaWeblog.editPost' );
  4457. $postdata = get_post( $post_ID, ARRAY_A );
  4458. /*
  4459. * If there is no post data for the give post id, stop now and return an error.
  4460. * Otherwise a new post will be created (which was the old behavior).
  4461. */
  4462. if ( ! $postdata || empty( $postdata[ 'ID' ] ) )
  4463. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  4464. if ( ! current_user_can( 'edit_post', $post_ID ) )
  4465. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  4466. // Use wp.editPost to edit post types other than post and page.
  4467. if ( ! in_array( $postdata[ 'post_type' ], array( 'post', 'page' ) ) )
  4468. return new IXR_Error( 401, __( 'Invalid post type.' ) );
  4469. // Thwart attempt to change the post type.
  4470. if ( ! empty( $content_struct[ 'post_type' ] ) && ( $content_struct['post_type'] != $postdata[ 'post_type' ] ) )
  4471. return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
  4472. // Check for a valid post format if one was given
  4473. if ( isset( $content_struct['wp_post_format'] ) ) {
  4474. $content_struct['wp_post_format'] = sanitize_key( $content_struct['wp_post_format'] );
  4475. if ( !array_key_exists( $content_struct['wp_post_format'], get_post_format_strings() ) ) {
  4476. return new IXR_Error( 404, __( 'Invalid post format.' ) );
  4477. }
  4478. }
  4479. $this->escape($postdata);
  4480. $ID = $postdata['ID'];
  4481. $post_content = $postdata['post_content'];
  4482. $post_title = $postdata['post_title'];
  4483. $post_excerpt = $postdata['post_excerpt'];
  4484. $post_password = $postdata['post_password'];
  4485. $post_parent = $postdata['post_parent'];
  4486. $post_type = $postdata['post_type'];
  4487. $menu_order = $postdata['menu_order'];
  4488. // Let WordPress manage slug if none was provided.
  4489. $post_name = $postdata['post_name'];
  4490. if ( isset($content_struct['wp_slug']) )
  4491. $post_name = $content_struct['wp_slug'];
  4492. // Only use a password if one was given.
  4493. if ( isset($content_struct['wp_password']) )
  4494. $post_password = $content_struct['wp_password'];
  4495. // Only set a post parent if one was given.
  4496. if ( isset($content_struct['wp_page_parent_id']) )
  4497. $post_parent = $content_struct['wp_page_parent_id'];
  4498. // Only set the menu_order if it was given.
  4499. if ( isset($content_struct['wp_page_order']) )
  4500. $menu_order = $content_struct['wp_page_order'];
  4501. $page_template = null;
  4502. if ( ! empty( $content_struct['wp_page_template'] ) && 'page' == $post_type )
  4503. $page_template = $content_struct['wp_page_template'];
  4504. $post_author = $postdata['post_author'];
  4505. // Only set the post_author if one is set.
  4506. if ( isset( $content_struct['wp_author_id'] ) ) {
  4507. // Check permissions if attempting to switch author to or from another user.
  4508. if ( $user->ID != $content_struct['wp_author_id'] || $user->ID != $post_author ) {
  4509. switch ( $post_type ) {
  4510. case 'post':
  4511. if ( ! current_user_can( 'edit_others_posts' ) ) {
  4512. return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the post author as this user.' ) );
  4513. }
  4514. break;
  4515. case 'page':
  4516. if ( ! current_user_can( 'edit_others_pages' ) ) {
  4517. return new IXR_Error( 401, __( 'Sorry, you are not allowed to change the page author as this user.' ) );
  4518. }
  4519. break;
  4520. default:
  4521. return new IXR_Error( 401, __( 'Invalid post type.' ) );
  4522. }
  4523. $post_author = $content_struct['wp_author_id'];
  4524. }
  4525. }
  4526. if ( isset($content_struct['mt_allow_comments']) ) {
  4527. if ( !is_numeric($content_struct['mt_allow_comments']) ) {
  4528. switch ( $content_struct['mt_allow_comments'] ) {
  4529. case 'closed':
  4530. $comment_status = 'closed';
  4531. break;
  4532. case 'open':
  4533. $comment_status = 'open';
  4534. break;
  4535. default:
  4536. $comment_status = get_default_comment_status( $post_type );
  4537. break;
  4538. }
  4539. } else {
  4540. switch ( (int) $content_struct['mt_allow_comments'] ) {
  4541. case 0:
  4542. case 2:
  4543. $comment_status = 'closed';
  4544. break;
  4545. case 1:
  4546. $comment_status = 'open';
  4547. break;
  4548. default:
  4549. $comment_status = get_default_comment_status( $post_type );
  4550. break;
  4551. }
  4552. }
  4553. }
  4554. if ( isset($content_struct['mt_allow_pings']) ) {
  4555. if ( !is_numeric($content_struct['mt_allow_pings']) ) {
  4556. switch ( $content_struct['mt_allow_pings'] ) {
  4557. case 'closed':
  4558. $ping_status = 'closed';
  4559. break;
  4560. case 'open':
  4561. $ping_status = 'open';
  4562. break;
  4563. default:
  4564. $ping_status = get_default_comment_status( $post_type, 'pingback' );
  4565. break;
  4566. }
  4567. } else {
  4568. switch ( (int) $content_struct["mt_allow_pings"] ) {
  4569. case 0:
  4570. $ping_status = 'closed';
  4571. break;
  4572. case 1:
  4573. $ping_status = 'open';
  4574. break;
  4575. default:
  4576. $ping_status = get_default_comment_status( $post_type, 'pingback' );
  4577. break;
  4578. }
  4579. }
  4580. }
  4581. if ( isset( $content_struct['title'] ) )
  4582. $post_title = $content_struct['title'];
  4583. if ( isset( $content_struct['description'] ) )
  4584. $post_content = $content_struct['description'];
  4585. $post_category = array();
  4586. if ( isset( $content_struct['categories'] ) ) {
  4587. $catnames = $content_struct['categories'];
  4588. if ( is_array($catnames) ) {
  4589. foreach ($catnames as $cat) {
  4590. $post_category[] = get_cat_ID($cat);
  4591. }
  4592. }
  4593. }
  4594. if ( isset( $content_struct['mt_excerpt'] ) )
  4595. $post_excerpt = $content_struct['mt_excerpt'];
  4596. $post_more = isset( $content_struct['mt_text_more'] ) ? $content_struct['mt_text_more'] : null;
  4597. $post_status = $publish ? 'publish' : 'draft';
  4598. if ( isset( $content_struct["{$post_type}_status"] ) ) {
  4599. switch( $content_struct["{$post_type}_status"] ) {
  4600. case 'draft':
  4601. case 'pending':
  4602. case 'private':
  4603. case 'publish':
  4604. $post_status = $content_struct["{$post_type}_status"];
  4605. break;
  4606. default:
  4607. $post_status = $publish ? 'publish' : 'draft';
  4608. break;
  4609. }
  4610. }
  4611. $tags_input = isset( $content_struct['mt_keywords'] ) ? $content_struct['mt_keywords'] : null;
  4612. if ( 'publish' == $post_status || 'private' == $post_status ) {
  4613. if ( 'page' == $post_type && ! current_user_can( 'publish_pages' ) ) {
  4614. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this page.' ) );
  4615. } elseif ( ! current_user_can( 'publish_posts' ) ) {
  4616. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish this post.' ) );
  4617. }
  4618. }
  4619. if ( $post_more )
  4620. $post_content = $post_content . "<!--more-->" . $post_more;
  4621. $to_ping = null;
  4622. if ( isset( $content_struct['mt_tb_ping_urls'] ) ) {
  4623. $to_ping = $content_struct['mt_tb_ping_urls'];
  4624. if ( is_array($to_ping) )
  4625. $to_ping = implode(' ', $to_ping);
  4626. }
  4627. // Do some timestamp voodoo.
  4628. if ( !empty( $content_struct['date_created_gmt'] ) )
  4629. // We know this is supposed to be GMT, so we're going to slap that Z on there by force.
  4630. $dateCreated = rtrim( $content_struct['date_created_gmt']->getIso(), 'Z' ) . 'Z';
  4631. elseif ( !empty( $content_struct['dateCreated']) )
  4632. $dateCreated = $content_struct['dateCreated']->getIso();
  4633. // Default to not flagging the post date to be edited unless it's intentional.
  4634. $edit_date = false;
  4635. if ( !empty( $dateCreated ) ) {
  4636. $post_date = get_date_from_gmt(iso8601_to_datetime($dateCreated));
  4637. $post_date_gmt = iso8601_to_datetime($dateCreated, 'GMT');
  4638. // Flag the post date to be edited.
  4639. $edit_date = true;
  4640. } else {
  4641. $post_date = $postdata['post_date'];
  4642. $post_date_gmt = $postdata['post_date_gmt'];
  4643. }
  4644. // We've got all the data -- post it.
  4645. $newpost = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'comment_status', 'ping_status', 'edit_date', 'post_date', 'post_date_gmt', 'to_ping', 'post_name', 'post_password', 'post_parent', 'menu_order', 'post_author', 'tags_input', 'page_template');
  4646. $result = wp_update_post($newpost, true);
  4647. if ( is_wp_error( $result ) )
  4648. return new IXR_Error(500, $result->get_error_message());
  4649. if ( !$result )
  4650. return new IXR_Error(500, __('Sorry, your entry could not be edited.'));
  4651. // Only posts can be sticky
  4652. if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
  4653. $data = $newpost;
  4654. $data['sticky'] = $content_struct['sticky'];
  4655. $data['post_type'] = 'post';
  4656. $error = $this->_toggle_sticky( $data, true );
  4657. if ( $error ) {
  4658. return $error;
  4659. }
  4660. }
  4661. if ( isset($content_struct['custom_fields']) )
  4662. $this->set_custom_fields($post_ID, $content_struct['custom_fields']);
  4663. if ( isset ( $content_struct['wp_post_thumbnail'] ) ) {
  4664. // Empty value deletes, non-empty value adds/updates.
  4665. if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
  4666. delete_post_thumbnail( $post_ID );
  4667. } else {
  4668. if ( set_post_thumbnail( $post_ID, $content_struct['wp_post_thumbnail'] ) === false )
  4669. return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  4670. }
  4671. unset( $content_struct['wp_post_thumbnail'] );
  4672. }
  4673. // Handle enclosures.
  4674. $thisEnclosure = isset($content_struct['enclosure']) ? $content_struct['enclosure'] : null;
  4675. $this->add_enclosure_if_new($post_ID, $thisEnclosure);
  4676. $this->attach_uploads( $ID, $post_content );
  4677. // Handle post formats if assigned, validation is handled earlier in this function.
  4678. if ( isset( $content_struct['wp_post_format'] ) )
  4679. set_post_format( $post_ID, $content_struct['wp_post_format'] );
  4680. /**
  4681. * Fires after a post has been successfully updated via the XML-RPC MovableType API.
  4682. *
  4683. * @since 3.4.0
  4684. *
  4685. * @param int $post_ID ID of the updated post.
  4686. * @param array $args An array of arguments to update the post.
  4687. */
  4688. do_action( 'xmlrpc_call_success_mw_editPost', $post_ID, $args );
  4689. return true;
  4690. }
  4691. /**
  4692. * Retrieve post.
  4693. *
  4694. * @since 1.5.0
  4695. *
  4696. * @param array $args {
  4697. * Method arguments. Note: arguments must be ordered as documented.
  4698. *
  4699. * @type int $blog_id (unused)
  4700. * @type int $post_ID
  4701. * @type string $username
  4702. * @type string $password
  4703. * }
  4704. * @return array|IXR_Error
  4705. */
  4706. public function mw_getPost( $args ) {
  4707. $this->escape( $args );
  4708. $post_ID = (int) $args[0];
  4709. $username = $args[1];
  4710. $password = $args[2];
  4711. if ( !$user = $this->login($username, $password) )
  4712. return $this->error;
  4713. $postdata = get_post($post_ID, ARRAY_A);
  4714. if ( ! $postdata )
  4715. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  4716. if ( !current_user_can( 'edit_post', $post_ID ) )
  4717. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  4718. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4719. do_action( 'xmlrpc_call', 'metaWeblog.getPost' );
  4720. if ($postdata['post_date'] != '') {
  4721. $post_date = $this->_convert_date( $postdata['post_date'] );
  4722. $post_date_gmt = $this->_convert_date_gmt( $postdata['post_date_gmt'], $postdata['post_date'] );
  4723. $post_modified = $this->_convert_date( $postdata['post_modified'] );
  4724. $post_modified_gmt = $this->_convert_date_gmt( $postdata['post_modified_gmt'], $postdata['post_modified'] );
  4725. $categories = array();
  4726. $catids = wp_get_post_categories($post_ID);
  4727. foreach ($catids as $catid)
  4728. $categories[] = get_cat_name($catid);
  4729. $tagnames = array();
  4730. $tags = wp_get_post_tags( $post_ID );
  4731. if ( !empty( $tags ) ) {
  4732. foreach ( $tags as $tag )
  4733. $tagnames[] = $tag->name;
  4734. $tagnames = implode( ', ', $tagnames );
  4735. } else {
  4736. $tagnames = '';
  4737. }
  4738. $post = get_extended($postdata['post_content']);
  4739. $link = get_permalink($postdata['ID']);
  4740. // Get the author info.
  4741. $author = get_userdata($postdata['post_author']);
  4742. $allow_comments = ('open' == $postdata['comment_status']) ? 1 : 0;
  4743. $allow_pings = ('open' == $postdata['ping_status']) ? 1 : 0;
  4744. // Consider future posts as published
  4745. if ( $postdata['post_status'] === 'future' )
  4746. $postdata['post_status'] = 'publish';
  4747. // Get post format
  4748. $post_format = get_post_format( $post_ID );
  4749. if ( empty( $post_format ) )
  4750. $post_format = 'standard';
  4751. $sticky = false;
  4752. if ( is_sticky( $post_ID ) )
  4753. $sticky = true;
  4754. $enclosure = array();
  4755. foreach ( (array) get_post_custom($post_ID) as $key => $val) {
  4756. if ($key == 'enclosure') {
  4757. foreach ( (array) $val as $enc ) {
  4758. $encdata = explode("\n", $enc);
  4759. $enclosure['url'] = trim(htmlspecialchars($encdata[0]));
  4760. $enclosure['length'] = (int) trim($encdata[1]);
  4761. $enclosure['type'] = trim($encdata[2]);
  4762. break 2;
  4763. }
  4764. }
  4765. }
  4766. $resp = array(
  4767. 'dateCreated' => $post_date,
  4768. 'userid' => $postdata['post_author'],
  4769. 'postid' => $postdata['ID'],
  4770. 'description' => $post['main'],
  4771. 'title' => $postdata['post_title'],
  4772. 'link' => $link,
  4773. 'permaLink' => $link,
  4774. // commented out because no other tool seems to use this
  4775. // 'content' => $entry['post_content'],
  4776. 'categories' => $categories,
  4777. 'mt_excerpt' => $postdata['post_excerpt'],
  4778. 'mt_text_more' => $post['extended'],
  4779. 'wp_more_text' => $post['more_text'],
  4780. 'mt_allow_comments' => $allow_comments,
  4781. 'mt_allow_pings' => $allow_pings,
  4782. 'mt_keywords' => $tagnames,
  4783. 'wp_slug' => $postdata['post_name'],
  4784. 'wp_password' => $postdata['post_password'],
  4785. 'wp_author_id' => (string) $author->ID,
  4786. 'wp_author_display_name' => $author->display_name,
  4787. 'date_created_gmt' => $post_date_gmt,
  4788. 'post_status' => $postdata['post_status'],
  4789. 'custom_fields' => $this->get_custom_fields($post_ID),
  4790. 'wp_post_format' => $post_format,
  4791. 'sticky' => $sticky,
  4792. 'date_modified' => $post_modified,
  4793. 'date_modified_gmt' => $post_modified_gmt
  4794. );
  4795. if ( !empty($enclosure) ) $resp['enclosure'] = $enclosure;
  4796. $resp['wp_post_thumbnail'] = get_post_thumbnail_id( $postdata['ID'] );
  4797. return $resp;
  4798. } else {
  4799. return new IXR_Error(404, __('Sorry, no such post.'));
  4800. }
  4801. }
  4802. /**
  4803. * Retrieve list of recent posts.
  4804. *
  4805. * @since 1.5.0
  4806. *
  4807. * @param array $args {
  4808. * Method arguments. Note: arguments must be ordered as documented.
  4809. *
  4810. * @type int $blog_id (unused)
  4811. * @type string $username
  4812. * @type string $password
  4813. * @type int $numberposts
  4814. * }
  4815. * @return array|IXR_Error
  4816. */
  4817. public function mw_getRecentPosts( $args ) {
  4818. $this->escape( $args );
  4819. $username = $args[1];
  4820. $password = $args[2];
  4821. if ( isset( $args[3] ) )
  4822. $query = array( 'numberposts' => absint( $args[3] ) );
  4823. else
  4824. $query = array();
  4825. if ( !$user = $this->login($username, $password) )
  4826. return $this->error;
  4827. if ( ! current_user_can( 'edit_posts' ) )
  4828. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts.' ) );
  4829. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4830. do_action( 'xmlrpc_call', 'metaWeblog.getRecentPosts' );
  4831. $posts_list = wp_get_recent_posts( $query );
  4832. if ( !$posts_list )
  4833. return array();
  4834. $recent_posts = array();
  4835. foreach ($posts_list as $entry) {
  4836. if ( !current_user_can( 'edit_post', $entry['ID'] ) )
  4837. continue;
  4838. $post_date = $this->_convert_date( $entry['post_date'] );
  4839. $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
  4840. $post_modified = $this->_convert_date( $entry['post_modified'] );
  4841. $post_modified_gmt = $this->_convert_date_gmt( $entry['post_modified_gmt'], $entry['post_modified'] );
  4842. $categories = array();
  4843. $catids = wp_get_post_categories($entry['ID']);
  4844. foreach ( $catids as $catid )
  4845. $categories[] = get_cat_name($catid);
  4846. $tagnames = array();
  4847. $tags = wp_get_post_tags( $entry['ID'] );
  4848. if ( !empty( $tags ) ) {
  4849. foreach ( $tags as $tag ) {
  4850. $tagnames[] = $tag->name;
  4851. }
  4852. $tagnames = implode( ', ', $tagnames );
  4853. } else {
  4854. $tagnames = '';
  4855. }
  4856. $post = get_extended($entry['post_content']);
  4857. $link = get_permalink($entry['ID']);
  4858. // Get the post author info.
  4859. $author = get_userdata($entry['post_author']);
  4860. $allow_comments = ('open' == $entry['comment_status']) ? 1 : 0;
  4861. $allow_pings = ('open' == $entry['ping_status']) ? 1 : 0;
  4862. // Consider future posts as published
  4863. if ( $entry['post_status'] === 'future' )
  4864. $entry['post_status'] = 'publish';
  4865. // Get post format
  4866. $post_format = get_post_format( $entry['ID'] );
  4867. if ( empty( $post_format ) )
  4868. $post_format = 'standard';
  4869. $recent_posts[] = array(
  4870. 'dateCreated' => $post_date,
  4871. 'userid' => $entry['post_author'],
  4872. 'postid' => (string) $entry['ID'],
  4873. 'description' => $post['main'],
  4874. 'title' => $entry['post_title'],
  4875. 'link' => $link,
  4876. 'permaLink' => $link,
  4877. // commented out because no other tool seems to use this
  4878. // 'content' => $entry['post_content'],
  4879. 'categories' => $categories,
  4880. 'mt_excerpt' => $entry['post_excerpt'],
  4881. 'mt_text_more' => $post['extended'],
  4882. 'wp_more_text' => $post['more_text'],
  4883. 'mt_allow_comments' => $allow_comments,
  4884. 'mt_allow_pings' => $allow_pings,
  4885. 'mt_keywords' => $tagnames,
  4886. 'wp_slug' => $entry['post_name'],
  4887. 'wp_password' => $entry['post_password'],
  4888. 'wp_author_id' => (string) $author->ID,
  4889. 'wp_author_display_name' => $author->display_name,
  4890. 'date_created_gmt' => $post_date_gmt,
  4891. 'post_status' => $entry['post_status'],
  4892. 'custom_fields' => $this->get_custom_fields($entry['ID']),
  4893. 'wp_post_format' => $post_format,
  4894. 'date_modified' => $post_modified,
  4895. 'date_modified_gmt' => $post_modified_gmt,
  4896. 'sticky' => ( $entry['post_type'] === 'post' && is_sticky( $entry['ID'] ) ),
  4897. 'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] )
  4898. );
  4899. }
  4900. return $recent_posts;
  4901. }
  4902. /**
  4903. * Retrieve the list of categories on a given blog.
  4904. *
  4905. * @since 1.5.0
  4906. *
  4907. * @param array $args {
  4908. * Method arguments. Note: arguments must be ordered as documented.
  4909. *
  4910. * @type int $blog_id (unused)
  4911. * @type string $username
  4912. * @type string $password
  4913. * }
  4914. * @return array|IXR_Error
  4915. */
  4916. public function mw_getCategories( $args ) {
  4917. $this->escape( $args );
  4918. $username = $args[1];
  4919. $password = $args[2];
  4920. if ( !$user = $this->login($username, $password) )
  4921. return $this->error;
  4922. if ( !current_user_can( 'edit_posts' ) )
  4923. return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
  4924. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4925. do_action( 'xmlrpc_call', 'metaWeblog.getCategories' );
  4926. $categories_struct = array();
  4927. if ( $cats = get_categories(array('get' => 'all')) ) {
  4928. foreach ( $cats as $cat ) {
  4929. $struct = array();
  4930. $struct['categoryId'] = $cat->term_id;
  4931. $struct['parentId'] = $cat->parent;
  4932. $struct['description'] = $cat->name;
  4933. $struct['categoryDescription'] = $cat->description;
  4934. $struct['categoryName'] = $cat->name;
  4935. $struct['htmlUrl'] = esc_html(get_category_link($cat->term_id));
  4936. $struct['rssUrl'] = esc_html(get_category_feed_link($cat->term_id, 'rss2'));
  4937. $categories_struct[] = $struct;
  4938. }
  4939. }
  4940. return $categories_struct;
  4941. }
  4942. /**
  4943. * Uploads a file, following your settings.
  4944. *
  4945. * Adapted from a patch by Johann Richard.
  4946. *
  4947. * @link http://mycvs.org/archives/2004/06/30/file-upload-to-wordpress-in-ecto/
  4948. *
  4949. * @since 1.5.0
  4950. *
  4951. * @global wpdb $wpdb WordPress database abstraction object.
  4952. *
  4953. * @param array $args {
  4954. * Method arguments. Note: arguments must be ordered as documented.
  4955. *
  4956. * @type int $blog_id (unused)
  4957. * @type string $username
  4958. * @type string $password
  4959. * @type array $data
  4960. * }
  4961. * @return array|IXR_Error
  4962. */
  4963. public function mw_newMediaObject( $args ) {
  4964. global $wpdb;
  4965. $username = $this->escape( $args[1] );
  4966. $password = $this->escape( $args[2] );
  4967. $data = $args[3];
  4968. $name = sanitize_file_name( $data['name'] );
  4969. $type = $data['type'];
  4970. $bits = $data['bits'];
  4971. if ( !$user = $this->login($username, $password) )
  4972. return $this->error;
  4973. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  4974. do_action( 'xmlrpc_call', 'metaWeblog.newMediaObject' );
  4975. if ( !current_user_can('upload_files') ) {
  4976. $this->error = new IXR_Error( 401, __( 'Sorry, you are not allowed to upload files.' ) );
  4977. return $this->error;
  4978. }
  4979. if ( is_multisite() && upload_is_user_over_quota( false ) ) {
  4980. $this->error = new IXR_Error( 401, __( 'Sorry, you have used your space allocation.' ) );
  4981. return $this->error;
  4982. }
  4983. /**
  4984. * Filters whether to preempt the XML-RPC media upload.
  4985. *
  4986. * Passing a truthy value will effectively short-circuit the media upload,
  4987. * returning that value as a 500 error instead.
  4988. *
  4989. * @since 2.1.0
  4990. *
  4991. * @param bool $error Whether to pre-empt the media upload. Default false.
  4992. */
  4993. if ( $upload_err = apply_filters( 'pre_upload_error', false ) ) {
  4994. return new IXR_Error( 500, $upload_err );
  4995. }
  4996. $upload = wp_upload_bits($name, null, $bits);
  4997. if ( ! empty($upload['error']) ) {
  4998. /* translators: 1: file name, 2: error message */
  4999. $errorString = sprintf( __( 'Could not write file %1$s (%2$s).' ), $name, $upload['error'] );
  5000. return new IXR_Error( 500, $errorString );
  5001. }
  5002. // Construct the attachment array
  5003. $post_id = 0;
  5004. if ( ! empty( $data['post_id'] ) ) {
  5005. $post_id = (int) $data['post_id'];
  5006. if ( ! current_user_can( 'edit_post', $post_id ) )
  5007. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  5008. }
  5009. $attachment = array(
  5010. 'post_title' => $name,
  5011. 'post_content' => '',
  5012. 'post_type' => 'attachment',
  5013. 'post_parent' => $post_id,
  5014. 'post_mime_type' => $type,
  5015. 'guid' => $upload[ 'url' ]
  5016. );
  5017. // Save the data
  5018. $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
  5019. wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
  5020. /**
  5021. * Fires after a new attachment has been added via the XML-RPC MovableType API.
  5022. *
  5023. * @since 3.4.0
  5024. *
  5025. * @param int $id ID of the new attachment.
  5026. * @param array $args An array of arguments to add the attachment.
  5027. */
  5028. do_action( 'xmlrpc_call_success_mw_newMediaObject', $id, $args );
  5029. $struct = $this->_prepare_media_item( get_post( $id ) );
  5030. // Deprecated values
  5031. $struct['id'] = $struct['attachment_id'];
  5032. $struct['file'] = $struct['title'];
  5033. $struct['url'] = $struct['link'];
  5034. return $struct;
  5035. }
  5036. /* MovableType API functions
  5037. * specs on http://www.movabletype.org/docs/mtmanual_programmatic.html
  5038. */
  5039. /**
  5040. * Retrieve the post titles of recent posts.
  5041. *
  5042. * @since 1.5.0
  5043. *
  5044. * @param array $args {
  5045. * Method arguments. Note: arguments must be ordered as documented.
  5046. *
  5047. * @type int $blog_id (unused)
  5048. * @type string $username
  5049. * @type string $password
  5050. * @type int $numberposts
  5051. * }
  5052. * @return array|IXR_Error
  5053. */
  5054. public function mt_getRecentPostTitles( $args ) {
  5055. $this->escape( $args );
  5056. $username = $args[1];
  5057. $password = $args[2];
  5058. if ( isset( $args[3] ) )
  5059. $query = array( 'numberposts' => absint( $args[3] ) );
  5060. else
  5061. $query = array();
  5062. if ( !$user = $this->login($username, $password) )
  5063. return $this->error;
  5064. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5065. do_action( 'xmlrpc_call', 'mt.getRecentPostTitles' );
  5066. $posts_list = wp_get_recent_posts( $query );
  5067. if ( !$posts_list ) {
  5068. $this->error = new IXR_Error(500, __('Either there are no posts, or something went wrong.'));
  5069. return $this->error;
  5070. }
  5071. $recent_posts = array();
  5072. foreach ($posts_list as $entry) {
  5073. if ( !current_user_can( 'edit_post', $entry['ID'] ) )
  5074. continue;
  5075. $post_date = $this->_convert_date( $entry['post_date'] );
  5076. $post_date_gmt = $this->_convert_date_gmt( $entry['post_date_gmt'], $entry['post_date'] );
  5077. $recent_posts[] = array(
  5078. 'dateCreated' => $post_date,
  5079. 'userid' => $entry['post_author'],
  5080. 'postid' => (string) $entry['ID'],
  5081. 'title' => $entry['post_title'],
  5082. 'post_status' => $entry['post_status'],
  5083. 'date_created_gmt' => $post_date_gmt
  5084. );
  5085. }
  5086. return $recent_posts;
  5087. }
  5088. /**
  5089. * Retrieve list of all categories on blog.
  5090. *
  5091. * @since 1.5.0
  5092. *
  5093. * @param array $args {
  5094. * Method arguments. Note: arguments must be ordered as documented.
  5095. *
  5096. * @type int $blog_id (unused)
  5097. * @type string $username
  5098. * @type string $password
  5099. * }
  5100. * @return array|IXR_Error
  5101. */
  5102. public function mt_getCategoryList( $args ) {
  5103. $this->escape( $args );
  5104. $username = $args[1];
  5105. $password = $args[2];
  5106. if ( !$user = $this->login($username, $password) )
  5107. return $this->error;
  5108. if ( !current_user_can( 'edit_posts' ) )
  5109. return new IXR_Error( 401, __( 'Sorry, you must be able to edit posts on this site in order to view categories.' ) );
  5110. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5111. do_action( 'xmlrpc_call', 'mt.getCategoryList' );
  5112. $categories_struct = array();
  5113. if ( $cats = get_categories(array('hide_empty' => 0, 'hierarchical' => 0)) ) {
  5114. foreach ( $cats as $cat ) {
  5115. $struct = array();
  5116. $struct['categoryId'] = $cat->term_id;
  5117. $struct['categoryName'] = $cat->name;
  5118. $categories_struct[] = $struct;
  5119. }
  5120. }
  5121. return $categories_struct;
  5122. }
  5123. /**
  5124. * Retrieve post categories.
  5125. *
  5126. * @since 1.5.0
  5127. *
  5128. * @param array $args {
  5129. * Method arguments. Note: arguments must be ordered as documented.
  5130. *
  5131. * @type int $post_ID
  5132. * @type string $username
  5133. * @type string $password
  5134. * }
  5135. * @return array|IXR_Error
  5136. */
  5137. public function mt_getPostCategories( $args ) {
  5138. $this->escape( $args );
  5139. $post_ID = (int) $args[0];
  5140. $username = $args[1];
  5141. $password = $args[2];
  5142. if ( !$user = $this->login($username, $password) )
  5143. return $this->error;
  5144. if ( ! get_post( $post_ID ) )
  5145. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  5146. if ( !current_user_can( 'edit_post', $post_ID ) )
  5147. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  5148. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5149. do_action( 'xmlrpc_call', 'mt.getPostCategories' );
  5150. $categories = array();
  5151. $catids = wp_get_post_categories(intval($post_ID));
  5152. // first listed category will be the primary category
  5153. $isPrimary = true;
  5154. foreach ( $catids as $catid ) {
  5155. $categories[] = array(
  5156. 'categoryName' => get_cat_name($catid),
  5157. 'categoryId' => (string) $catid,
  5158. 'isPrimary' => $isPrimary
  5159. );
  5160. $isPrimary = false;
  5161. }
  5162. return $categories;
  5163. }
  5164. /**
  5165. * Sets categories for a post.
  5166. *
  5167. * @since 1.5.0
  5168. *
  5169. * @param array $args {
  5170. * Method arguments. Note: arguments must be ordered as documented.
  5171. *
  5172. * @type int $post_ID
  5173. * @type string $username
  5174. * @type string $password
  5175. * @type array $categories
  5176. * }
  5177. * @return true|IXR_Error True on success.
  5178. */
  5179. public function mt_setPostCategories( $args ) {
  5180. $this->escape( $args );
  5181. $post_ID = (int) $args[0];
  5182. $username = $args[1];
  5183. $password = $args[2];
  5184. $categories = $args[3];
  5185. if ( !$user = $this->login($username, $password) )
  5186. return $this->error;
  5187. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5188. do_action( 'xmlrpc_call', 'mt.setPostCategories' );
  5189. if ( ! get_post( $post_ID ) )
  5190. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  5191. if ( !current_user_can('edit_post', $post_ID) )
  5192. return new IXR_Error(401, __('Sorry, you are not allowed to edit this post.'));
  5193. $catids = array();
  5194. foreach ( $categories as $cat ) {
  5195. $catids[] = $cat['categoryId'];
  5196. }
  5197. wp_set_post_categories($post_ID, $catids);
  5198. return true;
  5199. }
  5200. /**
  5201. * Retrieve an array of methods supported by this server.
  5202. *
  5203. * @since 1.5.0
  5204. *
  5205. * @return array
  5206. */
  5207. public function mt_supportedMethods() {
  5208. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5209. do_action( 'xmlrpc_call', 'mt.supportedMethods' );
  5210. return array_keys( $this->methods );
  5211. }
  5212. /**
  5213. * Retrieve an empty array because we don't support per-post text filters.
  5214. *
  5215. * @since 1.5.0
  5216. */
  5217. public function mt_supportedTextFilters() {
  5218. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5219. do_action( 'xmlrpc_call', 'mt.supportedTextFilters' );
  5220. /**
  5221. * Filters the MoveableType text filters list for XML-RPC.
  5222. *
  5223. * @since 2.2.0
  5224. *
  5225. * @param array $filters An array of text filters.
  5226. */
  5227. return apply_filters( 'xmlrpc_text_filters', array() );
  5228. }
  5229. /**
  5230. * Retrieve trackbacks sent to a given post.
  5231. *
  5232. * @since 1.5.0
  5233. *
  5234. * @global wpdb $wpdb WordPress database abstraction object.
  5235. *
  5236. * @param int $post_ID
  5237. * @return array|IXR_Error
  5238. */
  5239. public function mt_getTrackbackPings( $post_ID ) {
  5240. global $wpdb;
  5241. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5242. do_action( 'xmlrpc_call', 'mt.getTrackbackPings' );
  5243. $actual_post = get_post($post_ID, ARRAY_A);
  5244. if ( !$actual_post )
  5245. return new IXR_Error(404, __('Sorry, no such post.'));
  5246. $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
  5247. if ( !$comments )
  5248. return array();
  5249. $trackback_pings = array();
  5250. foreach ( $comments as $comment ) {
  5251. if ( 'trackback' == $comment->comment_type ) {
  5252. $content = $comment->comment_content;
  5253. $title = substr($content, 8, (strpos($content, '</strong>') - 8));
  5254. $trackback_pings[] = array(
  5255. 'pingTitle' => $title,
  5256. 'pingURL' => $comment->comment_author_url,
  5257. 'pingIP' => $comment->comment_author_IP
  5258. );
  5259. }
  5260. }
  5261. return $trackback_pings;
  5262. }
  5263. /**
  5264. * Sets a post's publish status to 'publish'.
  5265. *
  5266. * @since 1.5.0
  5267. *
  5268. * @param array $args {
  5269. * Method arguments. Note: arguments must be ordered as documented.
  5270. *
  5271. * @type int $post_ID
  5272. * @type string $username
  5273. * @type string $password
  5274. * }
  5275. * @return int|IXR_Error
  5276. */
  5277. public function mt_publishPost( $args ) {
  5278. $this->escape( $args );
  5279. $post_ID = (int) $args[0];
  5280. $username = $args[1];
  5281. $password = $args[2];
  5282. if ( !$user = $this->login($username, $password) )
  5283. return $this->error;
  5284. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5285. do_action( 'xmlrpc_call', 'mt.publishPost' );
  5286. $postdata = get_post($post_ID, ARRAY_A);
  5287. if ( ! $postdata )
  5288. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  5289. if ( !current_user_can('publish_posts') || !current_user_can('edit_post', $post_ID) )
  5290. return new IXR_Error(401, __('Sorry, you are not allowed to publish this post.'));
  5291. $postdata['post_status'] = 'publish';
  5292. // retain old cats
  5293. $cats = wp_get_post_categories($post_ID);
  5294. $postdata['post_category'] = $cats;
  5295. $this->escape($postdata);
  5296. return wp_update_post( $postdata );
  5297. }
  5298. /* PingBack functions
  5299. * specs on www.hixie.ch/specs/pingback/pingback
  5300. */
  5301. /**
  5302. * Retrieves a pingback and registers it.
  5303. *
  5304. * @since 1.5.0
  5305. *
  5306. * @param array $args {
  5307. * Method arguments. Note: arguments must be ordered as documented.
  5308. *
  5309. * @type string $pagelinkedfrom
  5310. * @type string $pagelinkedto
  5311. * }
  5312. * @return string|IXR_Error
  5313. */
  5314. public function pingback_ping( $args ) {
  5315. global $wpdb;
  5316. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5317. do_action( 'xmlrpc_call', 'pingback.ping' );
  5318. $this->escape( $args );
  5319. $pagelinkedfrom = str_replace( '&amp;', '&', $args[0] );
  5320. $pagelinkedto = str_replace( '&amp;', '&', $args[1] );
  5321. $pagelinkedto = str_replace( '&', '&amp;', $pagelinkedto );
  5322. /**
  5323. * Filters the pingback source URI.
  5324. *
  5325. * @since 3.6.0
  5326. *
  5327. * @param string $pagelinkedfrom URI of the page linked from.
  5328. * @param string $pagelinkedto URI of the page linked to.
  5329. */
  5330. $pagelinkedfrom = apply_filters( 'pingback_ping_source_uri', $pagelinkedfrom, $pagelinkedto );
  5331. if ( ! $pagelinkedfrom )
  5332. return $this->pingback_error( 0, __( 'A valid URL was not provided.' ) );
  5333. // Check if the page linked to is in our site
  5334. $pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
  5335. if ( !$pos1 )
  5336. return $this->pingback_error( 0, __( 'Is there no link to us?' ) );
  5337. // let's find which post is linked to
  5338. // FIXME: does url_to_postid() cover all these cases already?
  5339. // if so, then let's use it and drop the old code.
  5340. $urltest = parse_url($pagelinkedto);
  5341. if ( $post_ID = url_to_postid($pagelinkedto) ) {
  5342. // $way
  5343. } elseif ( isset( $urltest['path'] ) && preg_match('#p/[0-9]{1,}#', $urltest['path'], $match) ) {
  5344. // the path defines the post_ID (archives/p/XXXX)
  5345. $blah = explode('/', $match[0]);
  5346. $post_ID = (int) $blah[1];
  5347. } elseif ( isset( $urltest['query'] ) && preg_match('#p=[0-9]{1,}#', $urltest['query'], $match) ) {
  5348. // the querystring defines the post_ID (?p=XXXX)
  5349. $blah = explode('=', $match[0]);
  5350. $post_ID = (int) $blah[1];
  5351. } elseif ( isset($urltest['fragment']) ) {
  5352. // an #anchor is there, it's either...
  5353. if ( intval($urltest['fragment']) ) {
  5354. // ...an integer #XXXX (simplest case)
  5355. $post_ID = (int) $urltest['fragment'];
  5356. } elseif ( preg_match('/post-[0-9]+/',$urltest['fragment']) ) {
  5357. // ...a post id in the form 'post-###'
  5358. $post_ID = preg_replace('/[^0-9]+/', '', $urltest['fragment']);
  5359. } elseif ( is_string($urltest['fragment']) ) {
  5360. // ...or a string #title, a little more complicated
  5361. $title = preg_replace('/[^a-z0-9]/i', '.', $urltest['fragment']);
  5362. $sql = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_title RLIKE %s", $title );
  5363. if (! ($post_ID = $wpdb->get_var($sql)) ) {
  5364. // returning unknown error '0' is better than die()ing
  5365. return $this->pingback_error( 0, '' );
  5366. }
  5367. }
  5368. } else {
  5369. // TODO: Attempt to extract a post ID from the given URL
  5370. return $this->pingback_error( 33, __('The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
  5371. }
  5372. $post_ID = (int) $post_ID;
  5373. $post = get_post($post_ID);
  5374. if ( !$post ) // Post_ID not found
  5375. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
  5376. if ( $post_ID == url_to_postid($pagelinkedfrom) )
  5377. return $this->pingback_error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
  5378. // Check if pings are on
  5379. if ( !pings_open($post) )
  5380. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
  5381. // Let's check that the remote site didn't already pingback this entry
  5382. if ( $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_author_url = %s", $post_ID, $pagelinkedfrom) ) )
  5383. return $this->pingback_error( 48, __( 'The pingback has already been registered.' ) );
  5384. // very stupid, but gives time to the 'from' server to publish !
  5385. sleep(1);
  5386. $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
  5387. /** This filter is documented in wp-includes/class-http.php */
  5388. $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) );
  5389. // Let's check the remote site
  5390. $http_api_args = array(
  5391. 'timeout' => 10,
  5392. 'redirection' => 0,
  5393. 'limit_response_size' => 153600, // 150 KB
  5394. 'user-agent' => "$user_agent; verifying pingback from $remote_ip",
  5395. 'headers' => array(
  5396. 'X-Pingback-Forwarded-For' => $remote_ip,
  5397. ),
  5398. );
  5399. $request = wp_safe_remote_get( $pagelinkedfrom, $http_api_args );
  5400. $remote_source = $remote_source_original = wp_remote_retrieve_body( $request );
  5401. if ( ! $remote_source ) {
  5402. return $this->pingback_error( 16, __( 'The source URL does not exist.' ) );
  5403. }
  5404. /**
  5405. * Filters the pingback remote source.
  5406. *
  5407. * @since 2.5.0
  5408. *
  5409. * @param string $remote_source Response source for the page linked from.
  5410. * @param string $pagelinkedto URL of the page linked to.
  5411. */
  5412. $remote_source = apply_filters( 'pre_remote_source', $remote_source, $pagelinkedto );
  5413. // Work around bug in strip_tags():
  5414. $remote_source = str_replace( '<!DOC', '<DOC', $remote_source );
  5415. $remote_source = preg_replace( '/[\r\n\t ]+/', ' ', $remote_source ); // normalize spaces
  5416. $remote_source = preg_replace( "/<\/*(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $remote_source );
  5417. preg_match( '|<title>([^<]*?)</title>|is', $remote_source, $matchtitle );
  5418. $title = isset( $matchtitle[1] ) ? $matchtitle[1] : '';
  5419. if ( empty( $title ) ) {
  5420. return $this->pingback_error( 32, __( 'We cannot find a title on that page.' ) );
  5421. }
  5422. $remote_source = strip_tags( $remote_source, '<a>' ); // just keep the tag we need
  5423. $p = explode( "\n\n", $remote_source );
  5424. $preg_target = preg_quote($pagelinkedto, '|');
  5425. foreach ( $p as $para ) {
  5426. if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
  5427. preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
  5428. // If the URL isn't in a link context, keep looking
  5429. if ( empty($context) )
  5430. continue;
  5431. // We're going to use this fake tag to mark the context in a bit
  5432. // the marker is needed in case the link text appears more than once in the paragraph
  5433. $excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
  5434. // prevent really long link text
  5435. if ( strlen($context[1]) > 100 )
  5436. $context[1] = substr($context[1], 0, 100) . '&#8230;';
  5437. $marker = '<wpcontext>'.$context[1].'</wpcontext>'; // set up our marker
  5438. $excerpt= str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
  5439. $excerpt = strip_tags($excerpt, '<wpcontext>'); // strip all tags but our context marker
  5440. $excerpt = trim($excerpt);
  5441. $preg_marker = preg_quote($marker, '|');
  5442. $excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
  5443. $excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
  5444. break;
  5445. }
  5446. }
  5447. if ( empty($context) ) // Link to target not found
  5448. return $this->pingback_error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) );
  5449. $pagelinkedfrom = str_replace('&', '&amp;', $pagelinkedfrom);
  5450. $context = '[&#8230;] ' . esc_html( $excerpt ) . ' [&#8230;]';
  5451. $pagelinkedfrom = $this->escape( $pagelinkedfrom );
  5452. $comment_post_ID = (int) $post_ID;
  5453. $comment_author = $title;
  5454. $comment_author_email = '';
  5455. $this->escape($comment_author);
  5456. $comment_author_url = $pagelinkedfrom;
  5457. $comment_content = $context;
  5458. $this->escape($comment_content);
  5459. $comment_type = 'pingback';
  5460. $commentdata = compact(
  5461. 'comment_post_ID', 'comment_author', 'comment_author_url', 'comment_author_email',
  5462. 'comment_content', 'comment_type', 'remote_source', 'remote_source_original'
  5463. );
  5464. $comment_ID = wp_new_comment($commentdata);
  5465. if ( is_wp_error( $comment_ID ) ) {
  5466. return $this->pingback_error( 0, $comment_ID->get_error_message() );
  5467. }
  5468. /**
  5469. * Fires after a post pingback has been sent.
  5470. *
  5471. * @since 0.71
  5472. *
  5473. * @param int $comment_ID Comment ID.
  5474. */
  5475. do_action( 'pingback_post', $comment_ID );
  5476. /* translators: 1: URL of the page linked from, 2: URL of the page linked to */
  5477. return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $pagelinkedfrom, $pagelinkedto );
  5478. }
  5479. /**
  5480. * Retrieve array of URLs that pingbacked the given URL.
  5481. *
  5482. * Specs on http://www.aquarionics.com/misc/archives/blogite/0198.html
  5483. *
  5484. * @since 1.5.0
  5485. *
  5486. * @global wpdb $wpdb WordPress database abstraction object.
  5487. *
  5488. * @param string $url
  5489. * @return array|IXR_Error
  5490. */
  5491. public function pingback_extensions_getPingbacks( $url ) {
  5492. global $wpdb;
  5493. /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */
  5494. do_action( 'xmlrpc_call', 'pingback.extensions.getPingbacks' );
  5495. $url = $this->escape( $url );
  5496. $post_ID = url_to_postid($url);
  5497. if ( !$post_ID ) {
  5498. // We aren't sure that the resource is available and/or pingback enabled
  5499. return $this->pingback_error( 33, __( 'The specified target URL cannot be used as a target. It either doesn&#8217;t exist, or it is not a pingback-enabled resource.' ) );
  5500. }
  5501. $actual_post = get_post($post_ID, ARRAY_A);
  5502. if ( !$actual_post ) {
  5503. // No such post = resource not found
  5504. return $this->pingback_error( 32, __('The specified target URL does not exist.' ) );
  5505. }
  5506. $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_author_url, comment_content, comment_author_IP, comment_type FROM $wpdb->comments WHERE comment_post_ID = %d", $post_ID) );
  5507. if ( !$comments )
  5508. return array();
  5509. $pingbacks = array();
  5510. foreach ( $comments as $comment ) {
  5511. if ( 'pingback' == $comment->comment_type )
  5512. $pingbacks[] = $comment->comment_author_url;
  5513. }
  5514. return $pingbacks;
  5515. }
  5516. /**
  5517. * Sends a pingback error based on the given error code and message.
  5518. *
  5519. * @since 3.6.0
  5520. *
  5521. * @param int $code Error code.
  5522. * @param string $message Error message.
  5523. * @return IXR_Error Error object.
  5524. */
  5525. protected function pingback_error( $code, $message ) {
  5526. /**
  5527. * Filters the XML-RPC pingback error return.
  5528. *
  5529. * @since 3.5.1
  5530. *
  5531. * @param IXR_Error $error An IXR_Error object containing the error code and message.
  5532. */
  5533. return apply_filters( 'xmlrpc_pingback_error', new IXR_Error( $code, $message ) );
  5534. }
  5535. }