taxonomy.php 154 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643
  1. <?php
  2. /**
  3. * Core Taxonomy API
  4. *
  5. * @package WordPress
  6. * @subpackage Taxonomy
  7. */
  8. //
  9. // Taxonomy Registration
  10. //
  11. /**
  12. * Creates the initial taxonomies.
  13. *
  14. * This function fires twice: in wp-settings.php before plugins are loaded (for
  15. * backward compatibility reasons), and again on the {@see 'init'} action. We must
  16. * avoid registering rewrite rules before the {@see 'init'} action.
  17. *
  18. * @since 2.8.0
  19. *
  20. * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
  21. */
  22. function create_initial_taxonomies() {
  23. global $wp_rewrite;
  24. if ( ! did_action( 'init' ) ) {
  25. $rewrite = array(
  26. 'category' => false,
  27. 'post_tag' => false,
  28. 'post_format' => false,
  29. );
  30. } else {
  31. /**
  32. * Filters the post formats rewrite base.
  33. *
  34. * @since 3.1.0
  35. *
  36. * @param string $context Context of the rewrite base. Default 'type'.
  37. */
  38. $post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
  39. $rewrite = array(
  40. 'category' => array(
  41. 'hierarchical' => true,
  42. 'slug' => get_option( 'category_base' ) ? get_option( 'category_base' ) : 'category',
  43. 'with_front' => ! get_option( 'category_base' ) || $wp_rewrite->using_index_permalinks(),
  44. 'ep_mask' => EP_CATEGORIES,
  45. ),
  46. 'post_tag' => array(
  47. 'hierarchical' => false,
  48. 'slug' => get_option( 'tag_base' ) ? get_option( 'tag_base' ) : 'tag',
  49. 'with_front' => ! get_option( 'tag_base' ) || $wp_rewrite->using_index_permalinks(),
  50. 'ep_mask' => EP_TAGS,
  51. ),
  52. 'post_format' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
  53. );
  54. }
  55. register_taxonomy(
  56. 'category',
  57. 'post',
  58. array(
  59. 'hierarchical' => true,
  60. 'query_var' => 'category_name',
  61. 'rewrite' => $rewrite['category'],
  62. 'public' => true,
  63. 'show_ui' => true,
  64. 'show_admin_column' => true,
  65. '_builtin' => true,
  66. 'capabilities' => array(
  67. 'manage_terms' => 'manage_categories',
  68. 'edit_terms' => 'edit_categories',
  69. 'delete_terms' => 'delete_categories',
  70. 'assign_terms' => 'assign_categories',
  71. ),
  72. 'show_in_rest' => true,
  73. 'rest_base' => 'categories',
  74. 'rest_controller_class' => 'WP_REST_Terms_Controller',
  75. )
  76. );
  77. register_taxonomy(
  78. 'post_tag',
  79. 'post',
  80. array(
  81. 'hierarchical' => false,
  82. 'query_var' => 'tag',
  83. 'rewrite' => $rewrite['post_tag'],
  84. 'public' => true,
  85. 'show_ui' => true,
  86. 'show_admin_column' => true,
  87. '_builtin' => true,
  88. 'capabilities' => array(
  89. 'manage_terms' => 'manage_post_tags',
  90. 'edit_terms' => 'edit_post_tags',
  91. 'delete_terms' => 'delete_post_tags',
  92. 'assign_terms' => 'assign_post_tags',
  93. ),
  94. 'show_in_rest' => true,
  95. 'rest_base' => 'tags',
  96. 'rest_controller_class' => 'WP_REST_Terms_Controller',
  97. )
  98. );
  99. register_taxonomy(
  100. 'nav_menu',
  101. 'nav_menu_item',
  102. array(
  103. 'public' => false,
  104. 'hierarchical' => false,
  105. 'labels' => array(
  106. 'name' => __( 'Navigation Menus' ),
  107. 'singular_name' => __( 'Navigation Menu' ),
  108. ),
  109. 'query_var' => false,
  110. 'rewrite' => false,
  111. 'show_ui' => false,
  112. '_builtin' => true,
  113. 'show_in_nav_menus' => false,
  114. )
  115. );
  116. register_taxonomy(
  117. 'link_category',
  118. 'link',
  119. array(
  120. 'hierarchical' => false,
  121. 'labels' => array(
  122. 'name' => __( 'Link Categories' ),
  123. 'singular_name' => __( 'Link Category' ),
  124. 'search_items' => __( 'Search Link Categories' ),
  125. 'popular_items' => null,
  126. 'all_items' => __( 'All Link Categories' ),
  127. 'edit_item' => __( 'Edit Link Category' ),
  128. 'update_item' => __( 'Update Link Category' ),
  129. 'add_new_item' => __( 'Add New Link Category' ),
  130. 'new_item_name' => __( 'New Link Category Name' ),
  131. 'separate_items_with_commas' => null,
  132. 'add_or_remove_items' => null,
  133. 'choose_from_most_used' => null,
  134. 'back_to_items' => __( '&larr; Back to Link Categories' ),
  135. ),
  136. 'capabilities' => array(
  137. 'manage_terms' => 'manage_links',
  138. 'edit_terms' => 'manage_links',
  139. 'delete_terms' => 'manage_links',
  140. 'assign_terms' => 'manage_links',
  141. ),
  142. 'query_var' => false,
  143. 'rewrite' => false,
  144. 'public' => false,
  145. 'show_ui' => true,
  146. '_builtin' => true,
  147. )
  148. );
  149. register_taxonomy(
  150. 'post_format',
  151. 'post',
  152. array(
  153. 'public' => true,
  154. 'hierarchical' => false,
  155. 'labels' => array(
  156. 'name' => _x( 'Formats', 'post format' ),
  157. 'singular_name' => _x( 'Format', 'post format' ),
  158. ),
  159. 'query_var' => true,
  160. 'rewrite' => $rewrite['post_format'],
  161. 'show_ui' => false,
  162. '_builtin' => true,
  163. 'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
  164. )
  165. );
  166. }
  167. /**
  168. * Retrieves a list of registered taxonomy names or objects.
  169. *
  170. * @since 3.0.0
  171. *
  172. * @global array $wp_taxonomies The registered taxonomies.
  173. *
  174. * @param array $args Optional. An array of `key => value` arguments to match against the taxonomy objects.
  175. * Default empty array.
  176. * @param string $output Optional. The type of output to return in the array. Accepts either taxonomy 'names'
  177. * or 'objects'. Default 'names'.
  178. * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
  179. * one element from the array needs to match; 'and' means all elements must match.
  180. * Default 'and'.
  181. * @return string[]|WP_Taxonomy[] An array of taxonomy names or objects.
  182. */
  183. function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) {
  184. global $wp_taxonomies;
  185. $field = ( 'names' === $output ) ? 'name' : false;
  186. return wp_filter_object_list( $wp_taxonomies, $args, $operator, $field );
  187. }
  188. /**
  189. * Return the names or objects of the taxonomies which are registered for the requested object or object type, such as
  190. * a post object or post type name.
  191. *
  192. * Example:
  193. *
  194. * $taxonomies = get_object_taxonomies( 'post' );
  195. *
  196. * This results in:
  197. *
  198. * Array( 'category', 'post_tag' )
  199. *
  200. * @since 2.3.0
  201. *
  202. * @global array $wp_taxonomies The registered taxonomies.
  203. *
  204. * @param string|string[]|WP_Post $object Name of the type of taxonomy object, or an object (row from posts)
  205. * @param string $output Optional. The type of output to return in the array. Accepts either
  206. * 'names' or 'objects'. Default 'names'.
  207. * @return string[]|WP_Taxonomy[] The names or objects of all taxonomies of `$object_type`.
  208. */
  209. function get_object_taxonomies( $object, $output = 'names' ) {
  210. global $wp_taxonomies;
  211. if ( is_object( $object ) ) {
  212. if ( $object->post_type === 'attachment' ) {
  213. return get_attachment_taxonomies( $object, $output );
  214. }
  215. $object = $object->post_type;
  216. }
  217. $object = (array) $object;
  218. $taxonomies = array();
  219. foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
  220. if ( array_intersect( $object, (array) $tax_obj->object_type ) ) {
  221. if ( 'names' === $output ) {
  222. $taxonomies[] = $tax_name;
  223. } else {
  224. $taxonomies[ $tax_name ] = $tax_obj;
  225. }
  226. }
  227. }
  228. return $taxonomies;
  229. }
  230. /**
  231. * Retrieves the taxonomy object of $taxonomy.
  232. *
  233. * The get_taxonomy function will first check that the parameter string given
  234. * is a taxonomy object and if it is, it will return it.
  235. *
  236. * @since 2.3.0
  237. *
  238. * @global array $wp_taxonomies The registered taxonomies.
  239. *
  240. * @param string $taxonomy Name of taxonomy object to return.
  241. * @return WP_Taxonomy|false The Taxonomy Object or false if $taxonomy doesn't exist.
  242. */
  243. function get_taxonomy( $taxonomy ) {
  244. global $wp_taxonomies;
  245. if ( ! taxonomy_exists( $taxonomy ) ) {
  246. return false;
  247. }
  248. return $wp_taxonomies[ $taxonomy ];
  249. }
  250. /**
  251. * Determines whether the taxonomy name exists.
  252. *
  253. * Formerly is_taxonomy(), introduced in 2.3.0.
  254. *
  255. * For more information on this and similar theme functions, check out
  256. * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
  257. * Conditional Tags} article in the Theme Developer Handbook.
  258. *
  259. * @since 3.0.0
  260. *
  261. * @global array $wp_taxonomies The registered taxonomies.
  262. *
  263. * @param string $taxonomy Name of taxonomy object.
  264. * @return bool Whether the taxonomy exists.
  265. */
  266. function taxonomy_exists( $taxonomy ) {
  267. global $wp_taxonomies;
  268. return isset( $wp_taxonomies[ $taxonomy ] );
  269. }
  270. /**
  271. * Determines whether the taxonomy object is hierarchical.
  272. *
  273. * Checks to make sure that the taxonomy is an object first. Then Gets the
  274. * object, and finally returns the hierarchical value in the object.
  275. *
  276. * A false return value might also mean that the taxonomy does not exist.
  277. *
  278. * For more information on this and similar theme functions, check out
  279. * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
  280. * Conditional Tags} article in the Theme Developer Handbook.
  281. *
  282. * @since 2.3.0
  283. *
  284. * @param string $taxonomy Name of taxonomy object.
  285. * @return bool Whether the taxonomy is hierarchical.
  286. */
  287. function is_taxonomy_hierarchical( $taxonomy ) {
  288. if ( ! taxonomy_exists( $taxonomy ) ) {
  289. return false;
  290. }
  291. $taxonomy = get_taxonomy( $taxonomy );
  292. return $taxonomy->hierarchical;
  293. }
  294. /**
  295. * Creates or modifies a taxonomy object.
  296. *
  297. * Note: Do not use before the {@see 'init'} hook.
  298. *
  299. * A simple function for creating or modifying a taxonomy object based on
  300. * the parameters given. If modifying an existing taxonomy object, note
  301. * that the `$object_type` value from the original registration will be
  302. * overwritten.
  303. *
  304. * @since 2.3.0
  305. * @since 4.2.0 Introduced `show_in_quick_edit` argument.
  306. * @since 4.4.0 The `show_ui` argument is now enforced on the term editing screen.
  307. * @since 4.4.0 The `public` argument now controls whether the taxonomy can be queried on the front end.
  308. * @since 4.5.0 Introduced `publicly_queryable` argument.
  309. * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
  310. * arguments to register the Taxonomy in REST API.
  311. * @since 5.1.0 Introduced `meta_box_sanitize_cb` argument.
  312. *
  313. * @global array $wp_taxonomies Registered taxonomies.
  314. *
  315. * @param string $taxonomy Taxonomy key, must not exceed 32 characters.
  316. * @param array|string $object_type Object type or array of object types with which the taxonomy should be associated.
  317. * @param array|string $args {
  318. * Optional. Array or query string of arguments for registering a taxonomy.
  319. *
  320. * @type array $labels An array of labels for this taxonomy. By default, Tag labels are
  321. * used for non-hierarchical taxonomies, and Category labels are used
  322. * for hierarchical taxonomies. See accepted values in
  323. * get_taxonomy_labels(). Default empty array.
  324. * @type string $description A short descriptive summary of what the taxonomy is for. Default empty.
  325. * @type bool $public Whether a taxonomy is intended for use publicly either via
  326. * the admin interface or by front-end users. The default settings
  327. * of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
  328. * are inherited from `$public`.
  329. * @type bool $publicly_queryable Whether the taxonomy is publicly queryable.
  330. * If not set, the default is inherited from `$public`
  331. * @type bool $hierarchical Whether the taxonomy is hierarchical. Default false.
  332. * @type bool $show_ui Whether to generate and allow a UI for managing terms in this taxonomy in
  333. * the admin. If not set, the default is inherited from `$public`
  334. * (default true).
  335. * @type bool $show_in_menu Whether to show the taxonomy in the admin menu. If true, the taxonomy is
  336. * shown as a submenu of the object type menu. If false, no menu is shown.
  337. * `$show_ui` must be true. If not set, default is inherited from `$show_ui`
  338. * (default true).
  339. * @type bool $show_in_nav_menus Makes this taxonomy available for selection in navigation menus. If not
  340. * set, the default is inherited from `$public` (default true).
  341. * @type bool $show_in_rest Whether to include the taxonomy in the REST API. Set this to true
  342. * for the taxonomy to be available in the block editor.
  343. * @type string $rest_base To change the base url of REST API route. Default is $taxonomy.
  344. * @type string $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
  345. * @type bool $show_tagcloud Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
  346. * the default is inherited from `$show_ui` (default true).
  347. * @type bool $show_in_quick_edit Whether to show the taxonomy in the quick/bulk edit panel. It not set,
  348. * the default is inherited from `$show_ui` (default true).
  349. * @type bool $show_admin_column Whether to display a column for the taxonomy on its post type listing
  350. * screens. Default false.
  351. * @type bool|callable $meta_box_cb Provide a callback function for the meta box display. If not set,
  352. * post_categories_meta_box() is used for hierarchical taxonomies, and
  353. * post_tags_meta_box() is used for non-hierarchical. If false, no meta
  354. * box is shown.
  355. * @type callable $meta_box_sanitize_cb Callback function for sanitizing taxonomy data saved from a meta
  356. * box. If no callback is defined, an appropriate one is determined
  357. * based on the value of `$meta_box_cb`.
  358. * @type array $capabilities {
  359. * Array of capabilities for this taxonomy.
  360. *
  361. * @type string $manage_terms Default 'manage_categories'.
  362. * @type string $edit_terms Default 'manage_categories'.
  363. * @type string $delete_terms Default 'manage_categories'.
  364. * @type string $assign_terms Default 'edit_posts'.
  365. * }
  366. * @type bool|array $rewrite {
  367. * Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent
  368. * rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
  369. *
  370. * @type string $slug Customize the permastruct slug. Default `$taxonomy` key.
  371. * @type bool $with_front Should the permastruct be prepended with WP_Rewrite::$front. Default true.
  372. * @type bool $hierarchical Either hierarchical rewrite tag or not. Default false.
  373. * @type int $ep_mask Assign an endpoint mask. Default `EP_NONE`.
  374. * }
  375. * @type string|bool $query_var Sets the query var key for this taxonomy. Default `$taxonomy` key. If
  376. * false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
  377. * string, the query `?{query_var}={term_slug}` will be valid.
  378. * @type callable $update_count_callback Works much like a hook, in that it will be called when the count is
  379. * updated. Default _update_post_term_count() for taxonomies attached
  380. * to post types, which confirms that the objects are published before
  381. * counting them. Default _update_generic_term_count() for taxonomies
  382. * attached to other object types, such as users.
  383. * @type bool $_builtin This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
  384. * Default false.
  385. * }
  386. * @return WP_Error|void WP_Error, if errors.
  387. */
  388. function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
  389. global $wp_taxonomies;
  390. if ( ! is_array( $wp_taxonomies ) ) {
  391. $wp_taxonomies = array();
  392. }
  393. $args = wp_parse_args( $args );
  394. if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
  395. _doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2.0' );
  396. return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
  397. }
  398. $taxonomy_object = new WP_Taxonomy( $taxonomy, $object_type, $args );
  399. $taxonomy_object->add_rewrite_rules();
  400. $wp_taxonomies[ $taxonomy ] = $taxonomy_object;
  401. $taxonomy_object->add_hooks();
  402. /**
  403. * Fires after a taxonomy is registered.
  404. *
  405. * @since 3.3.0
  406. *
  407. * @param string $taxonomy Taxonomy slug.
  408. * @param array|string $object_type Object type or array of object types.
  409. * @param array $args Array of taxonomy registration arguments.
  410. */
  411. do_action( 'registered_taxonomy', $taxonomy, $object_type, (array) $taxonomy_object );
  412. }
  413. /**
  414. * Unregisters a taxonomy.
  415. *
  416. * Can not be used to unregister built-in taxonomies.
  417. *
  418. * @since 4.5.0
  419. *
  420. * @global WP $wp Current WordPress environment instance.
  421. * @global array $wp_taxonomies List of taxonomies.
  422. *
  423. * @param string $taxonomy Taxonomy name.
  424. * @return bool|WP_Error True on success, WP_Error on failure or if the taxonomy doesn't exist.
  425. */
  426. function unregister_taxonomy( $taxonomy ) {
  427. if ( ! taxonomy_exists( $taxonomy ) ) {
  428. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  429. }
  430. $taxonomy_object = get_taxonomy( $taxonomy );
  431. // Do not allow unregistering internal taxonomies.
  432. if ( $taxonomy_object->_builtin ) {
  433. return new WP_Error( 'invalid_taxonomy', __( 'Unregistering a built-in taxonomy is not allowed.' ) );
  434. }
  435. global $wp_taxonomies;
  436. $taxonomy_object->remove_rewrite_rules();
  437. $taxonomy_object->remove_hooks();
  438. // Remove the taxonomy.
  439. unset( $wp_taxonomies[ $taxonomy ] );
  440. /**
  441. * Fires after a taxonomy is unregistered.
  442. *
  443. * @since 4.5.0
  444. *
  445. * @param string $taxonomy Taxonomy name.
  446. */
  447. do_action( 'unregistered_taxonomy', $taxonomy );
  448. return true;
  449. }
  450. /**
  451. * Builds an object with all taxonomy labels out of a taxonomy object.
  452. *
  453. * @since 3.0.0
  454. * @since 4.3.0 Added the `no_terms` label.
  455. * @since 4.4.0 Added the `items_list_navigation` and `items_list` labels.
  456. * @since 4.9.0 Added the `most_used` and `back_to_items` labels.
  457. *
  458. * @param WP_Taxonomy $tax Taxonomy object.
  459. * @return object {
  460. * Taxonomy labels object. The first default value is for non-hierarchical taxonomies
  461. * (like tags) and the second one is for hierarchical taxonomies (like categories).
  462. *
  463. * @type string $name General name for the taxonomy, usually plural. The same
  464. * as and overridden by `$tax->label`. Default 'Tags'/'Categories'.
  465. * @type string $singular_name Name for one object of this taxonomy. Default 'Tag'/'Category'.
  466. * @type string $search_items Default 'Search Tags'/'Search Categories'.
  467. * @type string $popular_items This label is only used for non-hierarchical taxonomies.
  468. * Default 'Popular Tags'.
  469. * @type string $all_items Default 'All Tags'/'All Categories'.
  470. * @type string $parent_item This label is only used for hierarchical taxonomies. Default
  471. * 'Parent Category'.
  472. * @type string $parent_item_colon The same as `parent_item`, but with colon `:` in the end.
  473. * @type string $edit_item Default 'Edit Tag'/'Edit Category'.
  474. * @type string $view_item Default 'View Tag'/'View Category'.
  475. * @type string $update_item Default 'Update Tag'/'Update Category'.
  476. * @type string $add_new_item Default 'Add New Tag'/'Add New Category'.
  477. * @type string $new_item_name Default 'New Tag Name'/'New Category Name'.
  478. * @type string $separate_items_with_commas This label is only used for non-hierarchical taxonomies. Default
  479. * 'Separate tags with commas', used in the meta box.
  480. * @type string $add_or_remove_items This label is only used for non-hierarchical taxonomies. Default
  481. * 'Add or remove tags', used in the meta box when JavaScript
  482. * is disabled.
  483. * @type string $choose_from_most_used This label is only used on non-hierarchical taxonomies. Default
  484. * 'Choose from the most used tags', used in the meta box.
  485. * @type string $not_found Default 'No tags found'/'No categories found', used in
  486. * the meta box and taxonomy list table.
  487. * @type string $no_terms Default 'No tags'/'No categories', used in the posts and media
  488. * list tables.
  489. * @type string $items_list_navigation Label for the table pagination hidden heading.
  490. * @type string $items_list Label for the table hidden heading.
  491. * @type string $most_used Title for the Most Used tab. Default 'Most Used'.
  492. * @type string $back_to_items Label displayed after a term has been updated.
  493. * }
  494. */
  495. function get_taxonomy_labels( $tax ) {
  496. $tax->labels = (array) $tax->labels;
  497. if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) ) {
  498. $tax->labels['separate_items_with_commas'] = $tax->helps;
  499. }
  500. if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) ) {
  501. $tax->labels['not_found'] = $tax->no_tagcloud;
  502. }
  503. $nohier_vs_hier_defaults = array(
  504. 'name' => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
  505. 'singular_name' => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
  506. 'search_items' => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
  507. 'popular_items' => array( __( 'Popular Tags' ), null ),
  508. 'all_items' => array( __( 'All Tags' ), __( 'All Categories' ) ),
  509. 'parent_item' => array( null, __( 'Parent Category' ) ),
  510. 'parent_item_colon' => array( null, __( 'Parent Category:' ) ),
  511. 'edit_item' => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
  512. 'view_item' => array( __( 'View Tag' ), __( 'View Category' ) ),
  513. 'update_item' => array( __( 'Update Tag' ), __( 'Update Category' ) ),
  514. 'add_new_item' => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
  515. 'new_item_name' => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
  516. 'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
  517. 'add_or_remove_items' => array( __( 'Add or remove tags' ), null ),
  518. 'choose_from_most_used' => array( __( 'Choose from the most used tags' ), null ),
  519. 'not_found' => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
  520. 'no_terms' => array( __( 'No tags' ), __( 'No categories' ) ),
  521. 'items_list_navigation' => array( __( 'Tags list navigation' ), __( 'Categories list navigation' ) ),
  522. 'items_list' => array( __( 'Tags list' ), __( 'Categories list' ) ),
  523. /* translators: Tab heading when selecting from the most used terms. */
  524. 'most_used' => array( _x( 'Most Used', 'tags' ), _x( 'Most Used', 'categories' ) ),
  525. 'back_to_items' => array( __( '&larr; Back to Tags' ), __( '&larr; Back to Categories' ) ),
  526. );
  527. $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
  528. $labels = _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
  529. $taxonomy = $tax->name;
  530. $default_labels = clone $labels;
  531. /**
  532. * Filters the labels of a specific taxonomy.
  533. *
  534. * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
  535. *
  536. * @since 4.4.0
  537. *
  538. * @see get_taxonomy_labels() for the full list of taxonomy labels.
  539. *
  540. * @param object $labels Object with labels for the taxonomy as member variables.
  541. */
  542. $labels = apply_filters( "taxonomy_labels_{$taxonomy}", $labels );
  543. // Ensure that the filtered labels contain all required default values.
  544. $labels = (object) array_merge( (array) $default_labels, (array) $labels );
  545. return $labels;
  546. }
  547. /**
  548. * Add an already registered taxonomy to an object type.
  549. *
  550. * @since 3.0.0
  551. *
  552. * @global array $wp_taxonomies The registered taxonomies.
  553. *
  554. * @param string $taxonomy Name of taxonomy object.
  555. * @param string $object_type Name of the object type.
  556. * @return bool True if successful, false if not.
  557. */
  558. function register_taxonomy_for_object_type( $taxonomy, $object_type ) {
  559. global $wp_taxonomies;
  560. if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
  561. return false;
  562. }
  563. if ( ! get_post_type_object( $object_type ) ) {
  564. return false;
  565. }
  566. if ( ! in_array( $object_type, $wp_taxonomies[ $taxonomy ]->object_type ) ) {
  567. $wp_taxonomies[ $taxonomy ]->object_type[] = $object_type;
  568. }
  569. // Filter out empties.
  570. $wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
  571. /**
  572. * Fires after a taxonomy is registered for an object type.
  573. *
  574. * @since 5.1.0
  575. *
  576. * @param string $taxonomy Taxonomy name.
  577. * @param string $object_type Name of the object type.
  578. */
  579. do_action( 'registered_taxonomy_for_object_type', $taxonomy, $object_type );
  580. return true;
  581. }
  582. /**
  583. * Remove an already registered taxonomy from an object type.
  584. *
  585. * @since 3.7.0
  586. *
  587. * @global array $wp_taxonomies The registered taxonomies.
  588. *
  589. * @param string $taxonomy Name of taxonomy object.
  590. * @param string $object_type Name of the object type.
  591. * @return bool True if successful, false if not.
  592. */
  593. function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
  594. global $wp_taxonomies;
  595. if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
  596. return false;
  597. }
  598. if ( ! get_post_type_object( $object_type ) ) {
  599. return false;
  600. }
  601. $key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
  602. if ( false === $key ) {
  603. return false;
  604. }
  605. unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
  606. /**
  607. * Fires after a taxonomy is unregistered for an object type.
  608. *
  609. * @since 5.1.0
  610. *
  611. * @param string $taxonomy Taxonomy name.
  612. * @param string $object_type Name of the object type.
  613. */
  614. do_action( 'unregistered_taxonomy_for_object_type', $taxonomy, $object_type );
  615. return true;
  616. }
  617. //
  618. // Term API
  619. //
  620. /**
  621. * Retrieve object_ids of valid taxonomy and term.
  622. *
  623. * The strings of $taxonomies must exist before this function will continue. On
  624. * failure of finding a valid taxonomy, it will return an WP_Error class, kind
  625. * of like Exceptions in PHP 5, except you can't catch them. Even so, you can
  626. * still test for the WP_Error class and get the error message.
  627. *
  628. * The $terms aren't checked the same as $taxonomies, but still need to exist
  629. * for $object_ids to be returned.
  630. *
  631. * It is possible to change the order that object_ids is returned by either
  632. * using PHP sort family functions or using the database by using $args with
  633. * either ASC or DESC array. The value should be in the key named 'order'.
  634. *
  635. * @since 2.3.0
  636. *
  637. * @global wpdb $wpdb WordPress database abstraction object.
  638. *
  639. * @param int|array $term_ids Term id or array of term ids of terms that will be used.
  640. * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names.
  641. * @param array|string $args Change the order of the object_ids, either ASC or DESC.
  642. * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success.
  643. * the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found.
  644. */
  645. function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
  646. global $wpdb;
  647. if ( ! is_array( $term_ids ) ) {
  648. $term_ids = array( $term_ids );
  649. }
  650. if ( ! is_array( $taxonomies ) ) {
  651. $taxonomies = array( $taxonomies );
  652. }
  653. foreach ( (array) $taxonomies as $taxonomy ) {
  654. if ( ! taxonomy_exists( $taxonomy ) ) {
  655. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  656. }
  657. }
  658. $defaults = array( 'order' => 'ASC' );
  659. $args = wp_parse_args( $args, $defaults );
  660. $order = ( 'desc' === strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
  661. $term_ids = array_map( 'intval', $term_ids );
  662. $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
  663. $term_ids = "'" . implode( "', '", $term_ids ) . "'";
  664. $sql = "SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order";
  665. $last_changed = wp_cache_get_last_changed( 'terms' );
  666. $cache_key = 'get_objects_in_term:' . md5( $sql ) . ":$last_changed";
  667. $cache = wp_cache_get( $cache_key, 'terms' );
  668. if ( false === $cache ) {
  669. $object_ids = $wpdb->get_col( $sql );
  670. wp_cache_set( $cache_key, $object_ids, 'terms' );
  671. } else {
  672. $object_ids = (array) $cache;
  673. }
  674. if ( ! $object_ids ) {
  675. return array();
  676. }
  677. return $object_ids;
  678. }
  679. /**
  680. * Given a taxonomy query, generates SQL to be appended to a main query.
  681. *
  682. * @since 3.1.0
  683. *
  684. * @see WP_Tax_Query
  685. *
  686. * @param array $tax_query A compact tax query
  687. * @param string $primary_table
  688. * @param string $primary_id_column
  689. * @return array
  690. */
  691. function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
  692. $tax_query_obj = new WP_Tax_Query( $tax_query );
  693. return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
  694. }
  695. /**
  696. * Get all Term data from database by Term ID.
  697. *
  698. * The usage of the get_term function is to apply filters to a term object. It
  699. * is possible to get a term object from the database before applying the
  700. * filters.
  701. *
  702. * $term ID must be part of $taxonomy, to get from the database. Failure, might
  703. * be able to be captured by the hooks. Failure would be the same value as $wpdb
  704. * returns for the get_row method.
  705. *
  706. * There are two hooks, one is specifically for each term, named 'get_term', and
  707. * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
  708. * term object, and the taxonomy name as parameters. Both hooks are expected to
  709. * return a Term object.
  710. *
  711. * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
  712. * Must return term object. Used in get_term() as a catch-all filter for every
  713. * $term.
  714. *
  715. * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
  716. * name. Must return term object. $taxonomy will be the taxonomy name, so for
  717. * example, if 'category', it would be 'get_category' as the filter name. Useful
  718. * for custom taxonomies or plugging into default taxonomies.
  719. *
  720. * @todo Better formatting for DocBlock
  721. *
  722. * @since 2.3.0
  723. * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
  724. * The `$taxonomy` parameter was made optional.
  725. *
  726. * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
  727. *
  728. * @param int|WP_Term|object $term If integer, term data will be fetched from the database, or from the cache if
  729. * available. If stdClass object (as in the results of a database query), will apply
  730. * filters and return a `WP_Term` object corresponding to the `$term` data. If `WP_Term`,
  731. * will return `$term`.
  732. * @param string $taxonomy Optional. Taxonomy name that $term is part of.
  733. * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
  734. * a WP_Term object, an associative array, or a numeric array, respectively. Default OBJECT.
  735. * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
  736. * @return array|WP_Term|WP_Error|null Object of the type specified by `$output` on success. When `$output` is 'OBJECT',
  737. * a WP_Term instance is returned. If taxonomy does not exist, a WP_Error is
  738. * returned. Returns null for miscellaneous failure.
  739. */
  740. function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
  741. if ( empty( $term ) ) {
  742. return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
  743. }
  744. if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
  745. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  746. }
  747. if ( $term instanceof WP_Term ) {
  748. $_term = $term;
  749. } elseif ( is_object( $term ) ) {
  750. if ( empty( $term->filter ) || 'raw' === $term->filter ) {
  751. $_term = sanitize_term( $term, $taxonomy, 'raw' );
  752. $_term = new WP_Term( $_term );
  753. } else {
  754. $_term = WP_Term::get_instance( $term->term_id );
  755. }
  756. } else {
  757. $_term = WP_Term::get_instance( $term, $taxonomy );
  758. }
  759. if ( is_wp_error( $_term ) ) {
  760. return $_term;
  761. } elseif ( ! $_term ) {
  762. return null;
  763. }
  764. // Ensure for filters that this is not empty.
  765. $taxonomy = $_term->taxonomy;
  766. /**
  767. * Filters a taxonomy term object.
  768. *
  769. * @since 2.3.0
  770. * @since 4.4.0 `$_term` is now a `WP_Term` object.
  771. *
  772. * @param WP_Term $_term Term object.
  773. * @param string $taxonomy The taxonomy slug.
  774. */
  775. $_term = apply_filters( 'get_term', $_term, $taxonomy );
  776. /**
  777. * Filters a taxonomy term object.
  778. *
  779. * The dynamic portion of the filter name, `$taxonomy`, refers
  780. * to the slug of the term's taxonomy.
  781. *
  782. * @since 2.3.0
  783. * @since 4.4.0 `$_term` is now a `WP_Term` object.
  784. *
  785. * @param WP_Term $_term Term object.
  786. * @param string $taxonomy The taxonomy slug.
  787. */
  788. $_term = apply_filters( "get_{$taxonomy}", $_term, $taxonomy );
  789. // Bail if a filter callback has changed the type of the `$_term` object.
  790. if ( ! ( $_term instanceof WP_Term ) ) {
  791. return $_term;
  792. }
  793. // Sanitize term, according to the specified filter.
  794. $_term->filter( $filter );
  795. if ( $output === ARRAY_A ) {
  796. return $_term->to_array();
  797. } elseif ( $output === ARRAY_N ) {
  798. return array_values( $_term->to_array() );
  799. }
  800. return $_term;
  801. }
  802. /**
  803. * Get all Term data from database by Term field and data.
  804. *
  805. * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
  806. * required.
  807. *
  808. * The default $field is 'id', therefore it is possible to also use null for
  809. * field, but not recommended that you do so.
  810. *
  811. * If $value does not exist, the return value will be false. If $taxonomy exists
  812. * and $field and $value combinations exist, the Term will be returned.
  813. *
  814. * This function will always return the first term that matches the `$field`-
  815. * `$value`-`$taxonomy` combination specified in the parameters. If your query
  816. * is likely to match more than one term (as is likely to be the case when
  817. * `$field` is 'name', for example), consider using get_terms() instead; that
  818. * way, you will get all matching terms, and can provide your own logic for
  819. * deciding which one was intended.
  820. *
  821. * @todo Better formatting for DocBlock.
  822. *
  823. * @since 2.3.0
  824. * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
  825. * a WP_Term object if `$output` is `OBJECT`.
  826. *
  827. * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
  828. *
  829. * @param string $field Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'
  830. * @param string|int $value Search for this term value
  831. * @param string $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
  832. * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
  833. * a WP_Term object, an associative array, or a numeric array, respectively. Default OBJECT.
  834. * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
  835. * @return WP_Term|array|false WP_Term instance (or array) on success. Will return false if `$taxonomy` does not exist
  836. * or `$term` was not found.
  837. */
  838. function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
  839. // 'term_taxonomy_id' lookups don't require taxonomy checks.
  840. if ( 'term_taxonomy_id' !== $field && ! taxonomy_exists( $taxonomy ) ) {
  841. return false;
  842. }
  843. // No need to perform a query for empty 'slug' or 'name'.
  844. if ( 'slug' === $field || 'name' === $field ) {
  845. $value = (string) $value;
  846. if ( 0 === strlen( $value ) ) {
  847. return false;
  848. }
  849. }
  850. if ( 'id' === $field || 'term_id' === $field ) {
  851. $term = get_term( (int) $value, $taxonomy, $output, $filter );
  852. if ( is_wp_error( $term ) || null === $term ) {
  853. $term = false;
  854. }
  855. return $term;
  856. }
  857. $args = array(
  858. 'get' => 'all',
  859. 'number' => 1,
  860. 'taxonomy' => $taxonomy,
  861. 'update_term_meta_cache' => false,
  862. 'orderby' => 'none',
  863. 'suppress_filter' => true,
  864. );
  865. switch ( $field ) {
  866. case 'slug':
  867. $args['slug'] = $value;
  868. break;
  869. case 'name':
  870. $args['name'] = $value;
  871. break;
  872. case 'term_taxonomy_id':
  873. $args['term_taxonomy_id'] = $value;
  874. unset( $args['taxonomy'] );
  875. break;
  876. default:
  877. return false;
  878. }
  879. $terms = get_terms( $args );
  880. if ( is_wp_error( $terms ) || empty( $terms ) ) {
  881. return false;
  882. }
  883. $term = array_shift( $terms );
  884. // In the case of 'term_taxonomy_id', override the provided `$taxonomy` with whatever we find in the db.
  885. if ( 'term_taxonomy_id' === $field ) {
  886. $taxonomy = $term->taxonomy;
  887. }
  888. return get_term( $term, $taxonomy, $output, $filter );
  889. }
  890. /**
  891. * Merge all term children into a single array of their IDs.
  892. *
  893. * This recursive function will merge all of the children of $term into the same
  894. * array of term IDs. Only useful for taxonomies which are hierarchical.
  895. *
  896. * Will return an empty array if $term does not exist in $taxonomy.
  897. *
  898. * @since 2.3.0
  899. *
  900. * @param int $term_id ID of Term to get children.
  901. * @param string $taxonomy Taxonomy Name.
  902. * @return array|WP_Error List of Term IDs. WP_Error returned if `$taxonomy` does not exist.
  903. */
  904. function get_term_children( $term_id, $taxonomy ) {
  905. if ( ! taxonomy_exists( $taxonomy ) ) {
  906. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  907. }
  908. $term_id = intval( $term_id );
  909. $terms = _get_term_hierarchy( $taxonomy );
  910. if ( ! isset( $terms[ $term_id ] ) ) {
  911. return array();
  912. }
  913. $children = $terms[ $term_id ];
  914. foreach ( (array) $terms[ $term_id ] as $child ) {
  915. if ( $term_id === $child ) {
  916. continue;
  917. }
  918. if ( isset( $terms[ $child ] ) ) {
  919. $children = array_merge( $children, get_term_children( $child, $taxonomy ) );
  920. }
  921. }
  922. return $children;
  923. }
  924. /**
  925. * Get sanitized Term field.
  926. *
  927. * The function is for contextual reasons and for simplicity of usage.
  928. *
  929. * @since 2.3.0
  930. * @since 4.4.0 The `$taxonomy` parameter was made optional. `$term` can also now accept a WP_Term object.
  931. *
  932. * @see sanitize_term_field()
  933. *
  934. * @param string $field Term field to fetch.
  935. * @param int|WP_Term $term Term ID or object.
  936. * @param string $taxonomy Optional. Taxonomy Name. Default empty.
  937. * @param string $context Optional, default is display. Look at sanitize_term_field() for available options.
  938. * @return string|int|null|WP_Error Will return an empty string if $term is not an object or if $field is not set in $term.
  939. */
  940. function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) {
  941. $term = get_term( $term, $taxonomy );
  942. if ( is_wp_error( $term ) ) {
  943. return $term;
  944. }
  945. if ( ! is_object( $term ) ) {
  946. return '';
  947. }
  948. if ( ! isset( $term->$field ) ) {
  949. return '';
  950. }
  951. return sanitize_term_field( $field, $term->$field, $term->term_id, $term->taxonomy, $context );
  952. }
  953. /**
  954. * Sanitizes Term for editing.
  955. *
  956. * Return value is sanitize_term() and usage is for sanitizing the term for
  957. * editing. Function is for contextual and simplicity.
  958. *
  959. * @since 2.3.0
  960. *
  961. * @param int|object $id Term ID or object.
  962. * @param string $taxonomy Taxonomy name.
  963. * @return string|int|null|WP_Error Will return empty string if $term is not an object.
  964. */
  965. function get_term_to_edit( $id, $taxonomy ) {
  966. $term = get_term( $id, $taxonomy );
  967. if ( is_wp_error( $term ) ) {
  968. return $term;
  969. }
  970. if ( ! is_object( $term ) ) {
  971. return '';
  972. }
  973. return sanitize_term( $term, $taxonomy, 'edit' );
  974. }
  975. /**
  976. * Retrieve the terms in a given taxonomy or list of taxonomies.
  977. *
  978. * You can fully inject any customizations to the query before it is sent, as
  979. * well as control the output with a filter.
  980. *
  981. * The {@see 'get_terms'} filter will be called when the cache has the term and will
  982. * pass the found term along with the array of $taxonomies and array of $args.
  983. * This filter is also called before the array of terms is passed and will pass
  984. * the array of terms, along with the $taxonomies and $args.
  985. *
  986. * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
  987. * the $args.
  988. *
  989. * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
  990. * along with the $args array.
  991. *
  992. * Prior to 4.5.0, the first parameter of `get_terms()` was a taxonomy or list of taxonomies:
  993. *
  994. * $terms = get_terms( 'post_tag', array(
  995. * 'hide_empty' => false,
  996. * ) );
  997. *
  998. * Since 4.5.0, taxonomies should be passed via the 'taxonomy' argument in the `$args` array:
  999. *
  1000. * $terms = get_terms( array(
  1001. * 'taxonomy' => 'post_tag',
  1002. * 'hide_empty' => false,
  1003. * ) );
  1004. *
  1005. * @since 2.3.0
  1006. * @since 4.2.0 Introduced 'name' and 'childless' parameters.
  1007. * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
  1008. * Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
  1009. * a list of WP_Term objects.
  1010. * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
  1011. * Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
  1012. * @since 4.8.0 Introduced 'suppress_filter' parameter.
  1013. *
  1014. * @internal The `$deprecated` parameter is parsed for backward compatibility only.
  1015. *
  1016. * @param array|string $args Optional. Array or string of arguments. See WP_Term_Query::__construct()
  1017. * for information on accepted arguments. Default empty.
  1018. * @param array|string $deprecated Argument array, when using the legacy function parameter format. If present, this
  1019. * parameter will be interpreted as `$args`, and the first function parameter will
  1020. * be parsed as a taxonomy or array of taxonomies.
  1021. * @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of taxonomies
  1022. * do not exist.
  1023. */
  1024. function get_terms( $args = array(), $deprecated = '' ) {
  1025. $term_query = new WP_Term_Query();
  1026. $defaults = array(
  1027. 'suppress_filter' => false,
  1028. );
  1029. /*
  1030. * Legacy argument format ($taxonomy, $args) takes precedence.
  1031. *
  1032. * We detect legacy argument format by checking if
  1033. * (a) a second non-empty parameter is passed, or
  1034. * (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
  1035. */
  1036. $_args = wp_parse_args( $args );
  1037. $key_intersect = array_intersect_key( $term_query->query_var_defaults, (array) $_args );
  1038. $do_legacy_args = $deprecated || empty( $key_intersect );
  1039. if ( $do_legacy_args ) {
  1040. $taxonomies = (array) $args;
  1041. $args = wp_parse_args( $deprecated, $defaults );
  1042. $args['taxonomy'] = $taxonomies;
  1043. } else {
  1044. $args = wp_parse_args( $args, $defaults );
  1045. if ( isset( $args['taxonomy'] ) && null !== $args['taxonomy'] ) {
  1046. $args['taxonomy'] = (array) $args['taxonomy'];
  1047. }
  1048. }
  1049. if ( ! empty( $args['taxonomy'] ) ) {
  1050. foreach ( $args['taxonomy'] as $taxonomy ) {
  1051. if ( ! taxonomy_exists( $taxonomy ) ) {
  1052. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  1053. }
  1054. }
  1055. }
  1056. // Don't pass suppress_filter to WP_Term_Query.
  1057. $suppress_filter = $args['suppress_filter'];
  1058. unset( $args['suppress_filter'] );
  1059. $terms = $term_query->query( $args );
  1060. // Count queries are not filtered, for legacy reasons.
  1061. if ( ! is_array( $terms ) ) {
  1062. return $terms;
  1063. }
  1064. if ( $suppress_filter ) {
  1065. return $terms;
  1066. }
  1067. /**
  1068. * Filters the found terms.
  1069. *
  1070. * @since 2.3.0
  1071. * @since 4.6.0 Added the `$term_query` parameter.
  1072. *
  1073. * @param array $terms Array of found terms.
  1074. * @param array $taxonomies An array of taxonomies.
  1075. * @param array $args An array of get_terms() arguments.
  1076. * @param WP_Term_Query $term_query The WP_Term_Query object.
  1077. */
  1078. return apply_filters( 'get_terms', $terms, $term_query->query_vars['taxonomy'], $term_query->query_vars, $term_query );
  1079. }
  1080. /**
  1081. * Adds metadata to a term.
  1082. *
  1083. * @since 4.4.0
  1084. *
  1085. * @param int $term_id Term ID.
  1086. * @param string $meta_key Metadata name.
  1087. * @param mixed $meta_value Metadata value.
  1088. * @param bool $unique Optional. Whether to bail if an entry with the same key is found for the term.
  1089. * Default false.
  1090. * @return int|WP_Error|bool Meta ID on success. WP_Error when term_id is ambiguous between taxonomies.
  1091. * False on failure.
  1092. */
  1093. function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
  1094. if ( wp_term_is_shared( $term_id ) ) {
  1095. return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
  1096. }
  1097. return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
  1098. }
  1099. /**
  1100. * Removes metadata matching criteria from a term.
  1101. *
  1102. * @since 4.4.0
  1103. *
  1104. * @param int $term_id Term ID.
  1105. * @param string $meta_key Metadata name.
  1106. * @param mixed $meta_value Optional. Metadata value. If provided, rows will only be removed that match the value.
  1107. * @return bool True on success, false on failure.
  1108. */
  1109. function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
  1110. return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
  1111. }
  1112. /**
  1113. * Retrieves metadata for a term.
  1114. *
  1115. * @since 4.4.0
  1116. *
  1117. * @param int $term_id Term ID.
  1118. * @param string $key Optional. The meta key to retrieve. If no key is provided, fetches all metadata for the term.
  1119. * @param bool $single Whether to return a single value. If false, an array of all values matching the
  1120. * `$term_id`/`$key` pair will be returned. Default: false.
  1121. * @return mixed If `$single` is false, an array of metadata values. If `$single` is true, a single metadata value.
  1122. */
  1123. function get_term_meta( $term_id, $key = '', $single = false ) {
  1124. return get_metadata( 'term', $term_id, $key, $single );
  1125. }
  1126. /**
  1127. * Updates term metadata.
  1128. *
  1129. * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
  1130. *
  1131. * If the meta field for the term does not exist, it will be added.
  1132. *
  1133. * @since 4.4.0
  1134. *
  1135. * @param int $term_id Term ID.
  1136. * @param string $meta_key Metadata key.
  1137. * @param mixed $meta_value Metadata value.
  1138. * @param mixed $prev_value Optional. Previous value to check before removing.
  1139. * @return int|WP_Error|bool Meta ID if the key didn't previously exist. True on successful update.
  1140. * WP_Error when term_id is ambiguous between taxonomies. False on failure.
  1141. */
  1142. function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
  1143. if ( wp_term_is_shared( $term_id ) ) {
  1144. return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
  1145. }
  1146. return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
  1147. }
  1148. /**
  1149. * Updates metadata cache for list of term IDs.
  1150. *
  1151. * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
  1152. * Subsequent calls to `get_term_meta()` will not need to query the database.
  1153. *
  1154. * @since 4.4.0
  1155. *
  1156. * @param array $term_ids List of term IDs.
  1157. * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
  1158. */
  1159. function update_termmeta_cache( $term_ids ) {
  1160. return update_meta_cache( 'term', $term_ids );
  1161. }
  1162. /**
  1163. * Get all meta data, including meta IDs, for the given term ID.
  1164. *
  1165. * @since 4.9.0
  1166. *
  1167. * @global wpdb $wpdb WordPress database abstraction object.
  1168. *
  1169. * @param int $term_id Term ID.
  1170. * @return array|false Array with meta data, or false when the meta table is not installed.
  1171. */
  1172. function has_term_meta( $term_id ) {
  1173. $check = wp_check_term_meta_support_prefilter( null );
  1174. if ( null !== $check ) {
  1175. return $check;
  1176. }
  1177. global $wpdb;
  1178. return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, term_id FROM $wpdb->termmeta WHERE term_id = %d ORDER BY meta_key,meta_id", $term_id ), ARRAY_A );
  1179. }
  1180. /**
  1181. * Registers a meta key for terms.
  1182. *
  1183. * @since 4.9.8
  1184. *
  1185. * @param string $taxonomy Taxonomy to register a meta key for. Pass an empty string
  1186. * to register the meta key across all existing taxonomies.
  1187. * @param string $meta_key The meta key to register.
  1188. * @param array $args Data used to describe the meta key when registered. See
  1189. * {@see register_meta()} for a list of supported arguments.
  1190. * @return bool True if the meta key was successfully registered, false if not.
  1191. */
  1192. function register_term_meta( $taxonomy, $meta_key, array $args ) {
  1193. $args['object_subtype'] = $taxonomy;
  1194. return register_meta( 'term', $meta_key, $args );
  1195. }
  1196. /**
  1197. * Unregisters a meta key for terms.
  1198. *
  1199. * @since 4.9.8
  1200. *
  1201. * @param string $taxonomy Taxonomy the meta key is currently registered for. Pass
  1202. * an empty string if the meta key is registered across all
  1203. * existing taxonomies.
  1204. * @param string $meta_key The meta key to unregister.
  1205. * @return bool True on success, false if the meta key was not previously registered.
  1206. */
  1207. function unregister_term_meta( $taxonomy, $meta_key ) {
  1208. return unregister_meta_key( 'term', $meta_key, $taxonomy );
  1209. }
  1210. /**
  1211. * Determines whether a term exists.
  1212. *
  1213. * Formerly is_term(), introduced in 2.3.0.
  1214. *
  1215. * For more information on this and similar theme functions, check out
  1216. * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
  1217. * Conditional Tags} article in the Theme Developer Handbook.
  1218. *
  1219. * @since 3.0.0
  1220. *
  1221. * @global wpdb $wpdb WordPress database abstraction object.
  1222. *
  1223. * @param int|string $term The term to check. Accepts term ID, slug, or name.
  1224. * @param string $taxonomy Optional. The taxonomy name to use.
  1225. * @param int $parent Optional. ID of parent term under which to confine the exists search.
  1226. * @return mixed Returns null if the term does not exist. Returns the term ID
  1227. * if no taxonomy is specified and the term ID exists. Returns
  1228. * an array of the term ID and the term taxonomy ID if the taxonomy
  1229. * is specified and the pairing exists.
  1230. */
  1231. function term_exists( $term, $taxonomy = '', $parent = null ) {
  1232. global $wpdb;
  1233. $select = "SELECT term_id FROM $wpdb->terms as t WHERE ";
  1234. $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE ";
  1235. if ( is_int( $term ) ) {
  1236. if ( 0 === $term ) {
  1237. return 0;
  1238. }
  1239. $where = 't.term_id = %d';
  1240. if ( ! empty( $taxonomy ) ) {
  1241. // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
  1242. return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . ' AND tt.taxonomy = %s', $term, $taxonomy ), ARRAY_A );
  1243. } else {
  1244. return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
  1245. }
  1246. }
  1247. $term = trim( wp_unslash( $term ) );
  1248. $slug = sanitize_title( $term );
  1249. $where = 't.slug = %s';
  1250. $else_where = 't.name = %s';
  1251. $where_fields = array( $slug );
  1252. $else_where_fields = array( $term );
  1253. $orderby = 'ORDER BY t.term_id ASC';
  1254. $limit = 'LIMIT 1';
  1255. if ( ! empty( $taxonomy ) ) {
  1256. if ( is_numeric( $parent ) ) {
  1257. $parent = (int) $parent;
  1258. $where_fields[] = $parent;
  1259. $else_where_fields[] = $parent;
  1260. $where .= ' AND tt.parent = %d';
  1261. $else_where .= ' AND tt.parent = %d';
  1262. }
  1263. $where_fields[] = $taxonomy;
  1264. $else_where_fields[] = $taxonomy;
  1265. $result = $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields ), ARRAY_A );
  1266. if ( $result ) {
  1267. return $result;
  1268. }
  1269. return $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields ), ARRAY_A );
  1270. }
  1271. // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
  1272. $result = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields ) );
  1273. if ( $result ) {
  1274. return $result;
  1275. }
  1276. // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
  1277. return $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields ) );
  1278. }
  1279. /**
  1280. * Check if a term is an ancestor of another term.
  1281. *
  1282. * You can use either an id or the term object for both parameters.
  1283. *
  1284. * @since 3.4.0
  1285. *
  1286. * @param int|object $term1 ID or object to check if this is the parent term.
  1287. * @param int|object $term2 The child term.
  1288. * @param string $taxonomy Taxonomy name that $term1 and `$term2` belong to.
  1289. * @return bool Whether `$term2` is a child of `$term1`.
  1290. */
  1291. function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
  1292. if ( ! isset( $term1->term_id ) ) {
  1293. $term1 = get_term( $term1, $taxonomy );
  1294. }
  1295. if ( ! isset( $term2->parent ) ) {
  1296. $term2 = get_term( $term2, $taxonomy );
  1297. }
  1298. if ( empty( $term1->term_id ) || empty( $term2->parent ) ) {
  1299. return false;
  1300. }
  1301. if ( $term2->parent === $term1->term_id ) {
  1302. return true;
  1303. }
  1304. return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
  1305. }
  1306. /**
  1307. * Sanitize Term all fields.
  1308. *
  1309. * Relies on sanitize_term_field() to sanitize the term. The difference is that
  1310. * this function will sanitize <strong>all</strong> fields. The context is based
  1311. * on sanitize_term_field().
  1312. *
  1313. * The $term is expected to be either an array or an object.
  1314. *
  1315. * @since 2.3.0
  1316. *
  1317. * @param array|object $term The term to check.
  1318. * @param string $taxonomy The taxonomy name to use.
  1319. * @param string $context Optional. Context in which to sanitize the term. Accepts 'edit', 'db',
  1320. * 'display', 'attribute', or 'js'. Default 'display'.
  1321. * @return array|object Term with all fields sanitized.
  1322. */
  1323. function sanitize_term( $term, $taxonomy, $context = 'display' ) {
  1324. $fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
  1325. $do_object = is_object( $term );
  1326. $term_id = $do_object ? $term->term_id : ( isset( $term['term_id'] ) ? $term['term_id'] : 0 );
  1327. foreach ( (array) $fields as $field ) {
  1328. if ( $do_object ) {
  1329. if ( isset( $term->$field ) ) {
  1330. $term->$field = sanitize_term_field( $field, $term->$field, $term_id, $taxonomy, $context );
  1331. }
  1332. } else {
  1333. if ( isset( $term[ $field ] ) ) {
  1334. $term[ $field ] = sanitize_term_field( $field, $term[ $field ], $term_id, $taxonomy, $context );
  1335. }
  1336. }
  1337. }
  1338. if ( $do_object ) {
  1339. $term->filter = $context;
  1340. } else {
  1341. $term['filter'] = $context;
  1342. }
  1343. return $term;
  1344. }
  1345. /**
  1346. * Cleanse the field value in the term based on the context.
  1347. *
  1348. * Passing a term field value through the function should be assumed to have
  1349. * cleansed the value for whatever context the term field is going to be used.
  1350. *
  1351. * If no context or an unsupported context is given, then default filters will
  1352. * be applied.
  1353. *
  1354. * There are enough filters for each context to support a custom filtering
  1355. * without creating your own filter function. Simply create a function that
  1356. * hooks into the filter you need.
  1357. *
  1358. * @since 2.3.0
  1359. *
  1360. * @param string $field Term field to sanitize.
  1361. * @param string $value Search for this term value.
  1362. * @param int $term_id Term ID.
  1363. * @param string $taxonomy Taxonomy Name.
  1364. * @param string $context Context in which to sanitize the term field. Accepts 'edit', 'db', 'display',
  1365. * 'attribute', or 'js'.
  1366. * @return mixed Sanitized field.
  1367. */
  1368. function sanitize_term_field( $field, $value, $term_id, $taxonomy, $context ) {
  1369. $int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
  1370. if ( in_array( $field, $int_fields ) ) {
  1371. $value = (int) $value;
  1372. if ( $value < 0 ) {
  1373. $value = 0;
  1374. }
  1375. }
  1376. $context = strtolower( $context );
  1377. if ( 'raw' === $context ) {
  1378. return $value;
  1379. }
  1380. if ( 'edit' === $context ) {
  1381. /**
  1382. * Filters a term field to edit before it is sanitized.
  1383. *
  1384. * The dynamic portion of the filter name, `$field`, refers to the term field.
  1385. *
  1386. * @since 2.3.0
  1387. *
  1388. * @param mixed $value Value of the term field.
  1389. * @param int $term_id Term ID.
  1390. * @param string $taxonomy Taxonomy slug.
  1391. */
  1392. $value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
  1393. /**
  1394. * Filters the taxonomy field to edit before it is sanitized.
  1395. *
  1396. * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
  1397. * to the taxonomy slug and taxonomy field, respectively.
  1398. *
  1399. * @since 2.3.0
  1400. *
  1401. * @param mixed $value Value of the taxonomy field to edit.
  1402. * @param int $term_id Term ID.
  1403. */
  1404. $value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
  1405. if ( 'description' === $field ) {
  1406. $value = esc_html( $value ); // textarea_escaped
  1407. } else {
  1408. $value = esc_attr( $value );
  1409. }
  1410. } elseif ( 'db' === $context ) {
  1411. /**
  1412. * Filters a term field value before it is sanitized.
  1413. *
  1414. * The dynamic portion of the filter name, `$field`, refers to the term field.
  1415. *
  1416. * @since 2.3.0
  1417. *
  1418. * @param mixed $value Value of the term field.
  1419. * @param string $taxonomy Taxonomy slug.
  1420. */
  1421. $value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
  1422. /**
  1423. * Filters a taxonomy field before it is sanitized.
  1424. *
  1425. * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
  1426. * to the taxonomy slug and field name, respectively.
  1427. *
  1428. * @since 2.3.0
  1429. *
  1430. * @param mixed $value Value of the taxonomy field.
  1431. */
  1432. $value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
  1433. // Back compat filters
  1434. if ( 'slug' === $field ) {
  1435. /**
  1436. * Filters the category nicename before it is sanitized.
  1437. *
  1438. * Use the {@see 'pre_$taxonomy_$field'} hook instead.
  1439. *
  1440. * @since 2.0.3
  1441. *
  1442. * @param string $value The category nicename.
  1443. */
  1444. $value = apply_filters( 'pre_category_nicename', $value );
  1445. }
  1446. } elseif ( 'rss' === $context ) {
  1447. /**
  1448. * Filters the term field for use in RSS.
  1449. *
  1450. * The dynamic portion of the filter name, `$field`, refers to the term field.
  1451. *
  1452. * @since 2.3.0
  1453. *
  1454. * @param mixed $value Value of the term field.
  1455. * @param string $taxonomy Taxonomy slug.
  1456. */
  1457. $value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
  1458. /**
  1459. * Filters the taxonomy field for use in RSS.
  1460. *
  1461. * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
  1462. * to the taxonomy slug and field name, respectively.
  1463. *
  1464. * @since 2.3.0
  1465. *
  1466. * @param mixed $value Value of the taxonomy field.
  1467. */
  1468. $value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
  1469. } else {
  1470. // Use display filters by default.
  1471. /**
  1472. * Filters the term field sanitized for display.
  1473. *
  1474. * The dynamic portion of the filter name, `$field`, refers to the term field name.
  1475. *
  1476. * @since 2.3.0
  1477. *
  1478. * @param mixed $value Value of the term field.
  1479. * @param int $term_id Term ID.
  1480. * @param string $taxonomy Taxonomy slug.
  1481. * @param string $context Context to retrieve the term field value.
  1482. */
  1483. $value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
  1484. /**
  1485. * Filters the taxonomy field sanitized for display.
  1486. *
  1487. * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
  1488. * to the taxonomy slug and taxonomy field, respectively.
  1489. *
  1490. * @since 2.3.0
  1491. *
  1492. * @param mixed $value Value of the taxonomy field.
  1493. * @param int $term_id Term ID.
  1494. * @param string $context Context to retrieve the taxonomy field value.
  1495. */
  1496. $value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
  1497. }
  1498. if ( 'attribute' === $context ) {
  1499. $value = esc_attr( $value );
  1500. } elseif ( 'js' === $context ) {
  1501. $value = esc_js( $value );
  1502. }
  1503. return $value;
  1504. }
  1505. /**
  1506. * Count how many terms are in Taxonomy.
  1507. *
  1508. * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
  1509. *
  1510. * @since 2.3.0
  1511. *
  1512. * @param string $taxonomy Taxonomy name.
  1513. * @param array|string $args Optional. Array of arguments that get passed to get_terms().
  1514. * Default empty array.
  1515. * @return array|int|WP_Error Number of terms in that taxonomy or WP_Error if the taxonomy does not exist.
  1516. */
  1517. function wp_count_terms( $taxonomy, $args = array() ) {
  1518. $defaults = array(
  1519. 'taxonomy' => $taxonomy,
  1520. 'hide_empty' => false,
  1521. );
  1522. $args = wp_parse_args( $args, $defaults );
  1523. // backward compatibility
  1524. if ( isset( $args['ignore_empty'] ) ) {
  1525. $args['hide_empty'] = $args['ignore_empty'];
  1526. unset( $args['ignore_empty'] );
  1527. }
  1528. $args['fields'] = 'count';
  1529. return get_terms( $args );
  1530. }
  1531. /**
  1532. * Will unlink the object from the taxonomy or taxonomies.
  1533. *
  1534. * Will remove all relationships between the object and any terms in
  1535. * a particular taxonomy or taxonomies. Does not remove the term or
  1536. * taxonomy itself.
  1537. *
  1538. * @since 2.3.0
  1539. *
  1540. * @param int $object_id The term Object Id that refers to the term.
  1541. * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
  1542. */
  1543. function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
  1544. $object_id = (int) $object_id;
  1545. if ( ! is_array( $taxonomies ) ) {
  1546. $taxonomies = array( $taxonomies );
  1547. }
  1548. foreach ( (array) $taxonomies as $taxonomy ) {
  1549. $term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
  1550. $term_ids = array_map( 'intval', $term_ids );
  1551. wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
  1552. }
  1553. }
  1554. /**
  1555. * Removes a term from the database.
  1556. *
  1557. * If the term is a parent of other terms, then the children will be updated to
  1558. * that term's parent.
  1559. *
  1560. * Metadata associated with the term will be deleted.
  1561. *
  1562. * @since 2.3.0
  1563. *
  1564. * @global wpdb $wpdb WordPress database abstraction object.
  1565. *
  1566. * @param int $term Term ID.
  1567. * @param string $taxonomy Taxonomy Name.
  1568. * @param array|string $args {
  1569. * Optional. Array of arguments to override the default term ID. Default empty array.
  1570. *
  1571. * @type int $default The term ID to make the default term. This will only override
  1572. * the terms found if there is only one term found. Any other and
  1573. * the found terms are used.
  1574. * @type bool $force_default Optional. Whether to force the supplied term as default to be
  1575. * assigned even if the object was not going to be term-less.
  1576. * Default false.
  1577. * }
  1578. * @return bool|int|WP_Error True on success, false if term does not exist. Zero on attempted
  1579. * deletion of default Category. WP_Error if the taxonomy does not exist.
  1580. */
  1581. function wp_delete_term( $term, $taxonomy, $args = array() ) {
  1582. global $wpdb;
  1583. $term = (int) $term;
  1584. $ids = term_exists( $term, $taxonomy );
  1585. if ( ! $ids ) {
  1586. return false;
  1587. }
  1588. if ( is_wp_error( $ids ) ) {
  1589. return $ids;
  1590. }
  1591. $tt_id = $ids['term_taxonomy_id'];
  1592. $defaults = array();
  1593. if ( 'category' === $taxonomy ) {
  1594. $defaults['default'] = (int) get_option( 'default_category' );
  1595. if ( $defaults['default'] === $term ) {
  1596. return 0; // Don't delete the default category
  1597. }
  1598. }
  1599. $args = wp_parse_args( $args, $defaults );
  1600. if ( isset( $args['default'] ) ) {
  1601. $default = (int) $args['default'];
  1602. if ( ! term_exists( $default, $taxonomy ) ) {
  1603. unset( $default );
  1604. }
  1605. }
  1606. if ( isset( $args['force_default'] ) ) {
  1607. $force_default = $args['force_default'];
  1608. }
  1609. /**
  1610. * Fires when deleting a term, before any modifications are made to posts or terms.
  1611. *
  1612. * @since 4.1.0
  1613. *
  1614. * @param int $term Term ID.
  1615. * @param string $taxonomy Taxonomy Name.
  1616. */
  1617. do_action( 'pre_delete_term', $term, $taxonomy );
  1618. // Update children to point to new parent
  1619. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  1620. $term_obj = get_term( $term, $taxonomy );
  1621. if ( is_wp_error( $term_obj ) ) {
  1622. return $term_obj;
  1623. }
  1624. $parent = $term_obj->parent;
  1625. $edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int) $term_obj->term_id );
  1626. $edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
  1627. /**
  1628. * Fires immediately before a term to delete's children are reassigned a parent.
  1629. *
  1630. * @since 2.9.0
  1631. *
  1632. * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
  1633. */
  1634. do_action( 'edit_term_taxonomies', $edit_tt_ids );
  1635. $wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id ) + compact( 'taxonomy' ) );
  1636. // Clean the cache for all child terms.
  1637. $edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
  1638. clean_term_cache( $edit_term_ids, $taxonomy );
  1639. /**
  1640. * Fires immediately after a term to delete's children are reassigned a parent.
  1641. *
  1642. * @since 2.9.0
  1643. *
  1644. * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
  1645. */
  1646. do_action( 'edited_term_taxonomies', $edit_tt_ids );
  1647. }
  1648. // Get the term before deleting it or its term relationships so we can pass to actions below.
  1649. $deleted_term = get_term( $term, $taxonomy );
  1650. $object_ids = (array) $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
  1651. foreach ( $object_ids as $object_id ) {
  1652. $terms = wp_get_object_terms(
  1653. $object_id,
  1654. $taxonomy,
  1655. array(
  1656. 'fields' => 'ids',
  1657. 'orderby' => 'none',
  1658. )
  1659. );
  1660. if ( 1 === count( $terms ) && isset( $default ) ) {
  1661. $terms = array( $default );
  1662. } else {
  1663. $terms = array_diff( $terms, array( $term ) );
  1664. if ( isset( $default ) && isset( $force_default ) && $force_default ) {
  1665. $terms = array_merge( $terms, array( $default ) );
  1666. }
  1667. }
  1668. $terms = array_map( 'intval', $terms );
  1669. wp_set_object_terms( $object_id, $terms, $taxonomy );
  1670. }
  1671. // Clean the relationship caches for all object types using this term.
  1672. $tax_object = get_taxonomy( $taxonomy );
  1673. foreach ( $tax_object->object_type as $object_type ) {
  1674. clean_object_term_cache( $object_ids, $object_type );
  1675. }
  1676. $term_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->termmeta WHERE term_id = %d ", $term ) );
  1677. foreach ( $term_meta_ids as $mid ) {
  1678. delete_metadata_by_mid( 'term', $mid );
  1679. }
  1680. /**
  1681. * Fires immediately before a term taxonomy ID is deleted.
  1682. *
  1683. * @since 2.9.0
  1684. *
  1685. * @param int $tt_id Term taxonomy ID.
  1686. */
  1687. do_action( 'delete_term_taxonomy', $tt_id );
  1688. $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
  1689. /**
  1690. * Fires immediately after a term taxonomy ID is deleted.
  1691. *
  1692. * @since 2.9.0
  1693. *
  1694. * @param int $tt_id Term taxonomy ID.
  1695. */
  1696. do_action( 'deleted_term_taxonomy', $tt_id );
  1697. // Delete the term if no taxonomies use it.
  1698. if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term ) ) ) {
  1699. $wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
  1700. }
  1701. clean_term_cache( $term, $taxonomy );
  1702. /**
  1703. * Fires after a term is deleted from the database and the cache is cleaned.
  1704. *
  1705. * @since 2.5.0
  1706. * @since 4.5.0 Introduced the `$object_ids` argument.
  1707. *
  1708. * @param int $term Term ID.
  1709. * @param int $tt_id Term taxonomy ID.
  1710. * @param string $taxonomy Taxonomy slug.
  1711. * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
  1712. * by the parent function. WP_Error otherwise.
  1713. * @param array $object_ids List of term object IDs.
  1714. */
  1715. do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
  1716. /**
  1717. * Fires after a term in a specific taxonomy is deleted.
  1718. *
  1719. * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
  1720. * taxonomy the term belonged to.
  1721. *
  1722. * @since 2.3.0
  1723. * @since 4.5.0 Introduced the `$object_ids` argument.
  1724. *
  1725. * @param int $term Term ID.
  1726. * @param int $tt_id Term taxonomy ID.
  1727. * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
  1728. * by the parent function. WP_Error otherwise.
  1729. * @param array $object_ids List of term object IDs.
  1730. */
  1731. do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
  1732. return true;
  1733. }
  1734. /**
  1735. * Deletes one existing category.
  1736. *
  1737. * @since 2.0.0
  1738. *
  1739. * @param int $cat_ID Category term ID.
  1740. * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
  1741. * Zero on attempted deletion of default Category; WP_Error object is also a possibility.
  1742. */
  1743. function wp_delete_category( $cat_ID ) {
  1744. return wp_delete_term( $cat_ID, 'category' );
  1745. }
  1746. /**
  1747. * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
  1748. *
  1749. * @since 2.3.0
  1750. * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
  1751. * Introduced `$parent` argument.
  1752. * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments. When `$fields` is 'all' or
  1753. * 'all_with_object_id', an array of `WP_Term` objects will be returned.
  1754. * @since 4.7.0 Refactored to use WP_Term_Query, and to support any WP_Term_Query arguments.
  1755. *
  1756. * @param int|int[] $object_ids The ID(s) of the object(s) to retrieve.
  1757. * @param string|string[] $taxonomies The taxonomy names to retrieve terms from.
  1758. * @param array|string $args See WP_Term_Query::__construct() for supported arguments.
  1759. * @return array|WP_Error The requested term data or empty array if no terms found.
  1760. * WP_Error if any of the taxonomies don't exist.
  1761. */
  1762. function wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
  1763. if ( empty( $object_ids ) || empty( $taxonomies ) ) {
  1764. return array();
  1765. }
  1766. if ( ! is_array( $taxonomies ) ) {
  1767. $taxonomies = array( $taxonomies );
  1768. }
  1769. foreach ( $taxonomies as $taxonomy ) {
  1770. if ( ! taxonomy_exists( $taxonomy ) ) {
  1771. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  1772. }
  1773. }
  1774. if ( ! is_array( $object_ids ) ) {
  1775. $object_ids = array( $object_ids );
  1776. }
  1777. $object_ids = array_map( 'intval', $object_ids );
  1778. $args = wp_parse_args( $args );
  1779. /**
  1780. * Filter arguments for retrieving object terms.
  1781. *
  1782. * @since 4.9.0
  1783. *
  1784. * @param array $args An array of arguments for retrieving terms for the given object(s).
  1785. * See {@see wp_get_object_terms()} for details.
  1786. * @param int[] $object_ids Array of object IDs.
  1787. * @param string[] $taxonomies Array of taxonomy names to retrieve terms from.
  1788. */
  1789. $args = apply_filters( 'wp_get_object_terms_args', $args, $object_ids, $taxonomies );
  1790. /*
  1791. * When one or more queried taxonomies is registered with an 'args' array,
  1792. * those params override the `$args` passed to this function.
  1793. */
  1794. $terms = array();
  1795. if ( count( $taxonomies ) > 1 ) {
  1796. foreach ( $taxonomies as $index => $taxonomy ) {
  1797. $t = get_taxonomy( $taxonomy );
  1798. if ( isset( $t->args ) && is_array( $t->args ) && $args != array_merge( $args, $t->args ) ) {
  1799. unset( $taxonomies[ $index ] );
  1800. $terms = array_merge( $terms, wp_get_object_terms( $object_ids, $taxonomy, array_merge( $args, $t->args ) ) );
  1801. }
  1802. }
  1803. } else {
  1804. $t = get_taxonomy( $taxonomies[0] );
  1805. if ( isset( $t->args ) && is_array( $t->args ) ) {
  1806. $args = array_merge( $args, $t->args );
  1807. }
  1808. }
  1809. $args['taxonomy'] = $taxonomies;
  1810. $args['object_ids'] = $object_ids;
  1811. // Taxonomies registered without an 'args' param are handled here.
  1812. if ( ! empty( $taxonomies ) ) {
  1813. $terms_from_remaining_taxonomies = get_terms( $args );
  1814. // Array keys should be preserved for values of $fields that use term_id for keys.
  1815. if ( ! empty( $args['fields'] ) && 0 === strpos( $args['fields'], 'id=>' ) ) {
  1816. $terms = $terms + $terms_from_remaining_taxonomies;
  1817. } else {
  1818. $terms = array_merge( $terms, $terms_from_remaining_taxonomies );
  1819. }
  1820. }
  1821. /**
  1822. * Filters the terms for a given object or objects.
  1823. *
  1824. * @since 4.2.0
  1825. *
  1826. * @param array $terms Array of terms for the given object or objects.
  1827. * @param int[] $object_ids Array of object IDs for which terms were retrieved.
  1828. * @param string[] $taxonomies Array of taxonomy names from which terms were retrieved.
  1829. * @param array $args Array of arguments for retrieving terms for the given
  1830. * object(s). See wp_get_object_terms() for details.
  1831. */
  1832. $terms = apply_filters( 'get_object_terms', $terms, $object_ids, $taxonomies, $args );
  1833. $object_ids = implode( ',', $object_ids );
  1834. $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
  1835. /**
  1836. * Filters the terms for a given object or objects.
  1837. *
  1838. * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
  1839. * {@see 'get_object_terms'} filter is recommended as an alternative.
  1840. *
  1841. * @since 2.8.0
  1842. *
  1843. * @param array $terms Array of terms for the given object or objects.
  1844. * @param int[] $object_ids Array of object IDs for which terms were retrieved.
  1845. * @param string[] $taxonomies Array of taxonomy names from which terms were retrieved.
  1846. * @param array $args Array of arguments for retrieving terms for the given
  1847. * object(s). See wp_get_object_terms() for details.
  1848. */
  1849. return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
  1850. }
  1851. /**
  1852. * Add a new term to the database.
  1853. *
  1854. * A non-existent term is inserted in the following sequence:
  1855. * 1. The term is added to the term table, then related to the taxonomy.
  1856. * 2. If everything is correct, several actions are fired.
  1857. * 3. The 'term_id_filter' is evaluated.
  1858. * 4. The term cache is cleaned.
  1859. * 5. Several more actions are fired.
  1860. * 6. An array is returned containing the term_id and term_taxonomy_id.
  1861. *
  1862. * If the 'slug' argument is not empty, then it is checked to see if the term
  1863. * is invalid. If it is not a valid, existing term, it is added and the term_id
  1864. * is given.
  1865. *
  1866. * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
  1867. * the term is inserted and the term_id will be given.
  1868. *
  1869. * Error handling:
  1870. * If $taxonomy does not exist or $term is empty,
  1871. * a WP_Error object will be returned.
  1872. *
  1873. * If the term already exists on the same hierarchical level,
  1874. * or the term slug and name are not unique, a WP_Error object will be returned.
  1875. *
  1876. * @global wpdb $wpdb WordPress database abstraction object.
  1877. *
  1878. * @since 2.3.0
  1879. *
  1880. * @param string $term The term name to add or update.
  1881. * @param string $taxonomy The taxonomy to which to add the term.
  1882. * @param array|string $args {
  1883. * Optional. Array or string of arguments for inserting a term.
  1884. *
  1885. * @type string $alias_of Slug of the term to make this term an alias of.
  1886. * Default empty string. Accepts a term slug.
  1887. * @type string $description The term description. Default empty string.
  1888. * @type int $parent The id of the parent term. Default 0.
  1889. * @type string $slug The term slug to use. Default empty string.
  1890. * }
  1891. * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
  1892. * WP_Error otherwise.
  1893. */
  1894. function wp_insert_term( $term, $taxonomy, $args = array() ) {
  1895. global $wpdb;
  1896. if ( ! taxonomy_exists( $taxonomy ) ) {
  1897. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  1898. }
  1899. /**
  1900. * Filters a term before it is sanitized and inserted into the database.
  1901. *
  1902. * @since 3.0.0
  1903. *
  1904. * @param string|WP_Error $term The term name to add or update, or a WP_Error object if there's an error.
  1905. * @param string $taxonomy Taxonomy slug.
  1906. */
  1907. $term = apply_filters( 'pre_insert_term', $term, $taxonomy );
  1908. if ( is_wp_error( $term ) ) {
  1909. return $term;
  1910. }
  1911. if ( is_int( $term ) && 0 === $term ) {
  1912. return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
  1913. }
  1914. if ( '' === trim( $term ) ) {
  1915. return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
  1916. }
  1917. $defaults = array(
  1918. 'alias_of' => '',
  1919. 'description' => '',
  1920. 'parent' => 0,
  1921. 'slug' => '',
  1922. );
  1923. $args = wp_parse_args( $args, $defaults );
  1924. if ( $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
  1925. return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
  1926. }
  1927. $args['name'] = $term;
  1928. $args['taxonomy'] = $taxonomy;
  1929. // Coerce null description to strings, to avoid database errors.
  1930. $args['description'] = (string) $args['description'];
  1931. $args = sanitize_term( $args, $taxonomy, 'db' );
  1932. // expected_slashed ($name)
  1933. $name = wp_unslash( $args['name'] );
  1934. $description = wp_unslash( $args['description'] );
  1935. $parent = (int) $args['parent'];
  1936. $slug_provided = ! empty( $args['slug'] );
  1937. if ( ! $slug_provided ) {
  1938. $slug = sanitize_title( $name );
  1939. } else {
  1940. $slug = $args['slug'];
  1941. }
  1942. $term_group = 0;
  1943. if ( $args['alias_of'] ) {
  1944. $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
  1945. if ( ! empty( $alias->term_group ) ) {
  1946. // The alias we want is already in a group, so let's use that one.
  1947. $term_group = $alias->term_group;
  1948. } elseif ( ! empty( $alias->term_id ) ) {
  1949. /*
  1950. * The alias is not in a group, so we create a new one
  1951. * and add the alias to it.
  1952. */
  1953. $term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
  1954. wp_update_term(
  1955. $alias->term_id,
  1956. $taxonomy,
  1957. array(
  1958. 'term_group' => $term_group,
  1959. )
  1960. );
  1961. }
  1962. }
  1963. /*
  1964. * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
  1965. * unless a unique slug has been explicitly provided.
  1966. */
  1967. $name_matches = get_terms(
  1968. array(
  1969. 'taxonomy' => $taxonomy,
  1970. 'name' => $name,
  1971. 'hide_empty' => false,
  1972. 'parent' => $args['parent'],
  1973. 'update_term_meta_cache' => false,
  1974. )
  1975. );
  1976. /*
  1977. * The `name` match in `get_terms()` doesn't differentiate accented characters,
  1978. * so we do a stricter comparison here.
  1979. */
  1980. $name_match = null;
  1981. if ( $name_matches ) {
  1982. foreach ( $name_matches as $_match ) {
  1983. if ( strtolower( $name ) === strtolower( $_match->name ) ) {
  1984. $name_match = $_match;
  1985. break;
  1986. }
  1987. }
  1988. }
  1989. if ( $name_match ) {
  1990. $slug_match = get_term_by( 'slug', $slug, $taxonomy );
  1991. if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
  1992. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  1993. $siblings = get_terms(
  1994. array(
  1995. 'taxonomy' => $taxonomy,
  1996. 'get' => 'all',
  1997. 'parent' => $parent,
  1998. 'update_term_meta_cache' => false,
  1999. )
  2000. );
  2001. $existing_term = null;
  2002. if ( ( ! $slug_provided || $name_match->slug === $slug ) && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) {
  2003. $existing_term = $name_match;
  2004. } elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) {
  2005. $existing_term = $slug_match;
  2006. }
  2007. if ( $existing_term ) {
  2008. return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
  2009. }
  2010. } else {
  2011. return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
  2012. }
  2013. }
  2014. }
  2015. $slug = wp_unique_term_slug( $slug, (object) $args );
  2016. $data = compact( 'name', 'slug', 'term_group' );
  2017. /**
  2018. * Filters term data before it is inserted into the database.
  2019. *
  2020. * @since 4.7.0
  2021. *
  2022. * @param array $data Term data to be inserted.
  2023. * @param string $taxonomy Taxonomy slug.
  2024. * @param array $args Arguments passed to wp_insert_term().
  2025. */
  2026. $data = apply_filters( 'wp_insert_term_data', $data, $taxonomy, $args );
  2027. if ( false === $wpdb->insert( $wpdb->terms, $data ) ) {
  2028. return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database.' ), $wpdb->last_error );
  2029. }
  2030. $term_id = (int) $wpdb->insert_id;
  2031. // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
  2032. if ( empty( $slug ) ) {
  2033. $slug = sanitize_title( $slug, $term_id );
  2034. /** This action is documented in wp-includes/taxonomy.php */
  2035. do_action( 'edit_terms', $term_id, $taxonomy );
  2036. $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
  2037. /** This action is documented in wp-includes/taxonomy.php */
  2038. do_action( 'edited_terms', $term_id, $taxonomy );
  2039. }
  2040. $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
  2041. if ( ! empty( $tt_id ) ) {
  2042. return array(
  2043. 'term_id' => $term_id,
  2044. 'term_taxonomy_id' => $tt_id,
  2045. );
  2046. }
  2047. if ( false === $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ) + array( 'count' => 0 ) ) ) {
  2048. return new WP_Error( 'db_insert_error', __( 'Could not insert term taxonomy into the database.' ), $wpdb->last_error );
  2049. }
  2050. $tt_id = (int) $wpdb->insert_id;
  2051. /*
  2052. * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
  2053. * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
  2054. * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
  2055. * are not fired.
  2056. */
  2057. $duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id ) );
  2058. /**
  2059. * Filters the duplicate term check that takes place during term creation.
  2060. *
  2061. * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
  2062. * performs a last-minute confirmation of this uniqueness before allowing a new term
  2063. * to be created. Plugins with different uniqueness requirements may use this filter
  2064. * to bypass or modify the duplicate-term check.
  2065. *
  2066. * @since 5.1.0
  2067. *
  2068. * @param object $duplicate_term Duplicate term row from terms table, if found.
  2069. * @param string $term Term being inserted.
  2070. * @param string $taxonomy Taxonomy name.
  2071. * @param array $args Term arguments passed to the function.
  2072. * @param int $tt_id term_taxonomy_id for the newly created term.
  2073. */
  2074. $duplicate_term = apply_filters( 'wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id );
  2075. if ( $duplicate_term ) {
  2076. $wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
  2077. $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
  2078. $term_id = (int) $duplicate_term->term_id;
  2079. $tt_id = (int) $duplicate_term->term_taxonomy_id;
  2080. clean_term_cache( $term_id, $taxonomy );
  2081. return array(
  2082. 'term_id' => $term_id,
  2083. 'term_taxonomy_id' => $tt_id,
  2084. );
  2085. }
  2086. /**
  2087. * Fires immediately after a new term is created, before the term cache is cleaned.
  2088. *
  2089. * @since 2.3.0
  2090. *
  2091. * @param int $term_id Term ID.
  2092. * @param int $tt_id Term taxonomy ID.
  2093. * @param string $taxonomy Taxonomy slug.
  2094. */
  2095. do_action( 'create_term', $term_id, $tt_id, $taxonomy );
  2096. /**
  2097. * Fires after a new term is created for a specific taxonomy.
  2098. *
  2099. * The dynamic portion of the hook name, `$taxonomy`, refers
  2100. * to the slug of the taxonomy the term was created for.
  2101. *
  2102. * @since 2.3.0
  2103. *
  2104. * @param int $term_id Term ID.
  2105. * @param int $tt_id Term taxonomy ID.
  2106. */
  2107. do_action( "create_{$taxonomy}", $term_id, $tt_id );
  2108. /**
  2109. * Filters the term ID after a new term is created.
  2110. *
  2111. * @since 2.3.0
  2112. *
  2113. * @param int $term_id Term ID.
  2114. * @param int $tt_id Taxonomy term ID.
  2115. */
  2116. $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
  2117. clean_term_cache( $term_id, $taxonomy );
  2118. /**
  2119. * Fires after a new term is created, and after the term cache has been cleaned.
  2120. *
  2121. * @since 2.3.0
  2122. *
  2123. * @param int $term_id Term ID.
  2124. * @param int $tt_id Term taxonomy ID.
  2125. * @param string $taxonomy Taxonomy slug.
  2126. */
  2127. do_action( 'created_term', $term_id, $tt_id, $taxonomy );
  2128. /**
  2129. * Fires after a new term in a specific taxonomy is created, and after the term
  2130. * cache has been cleaned.
  2131. *
  2132. * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
  2133. *
  2134. * @since 2.3.0
  2135. *
  2136. * @param int $term_id Term ID.
  2137. * @param int $tt_id Term taxonomy ID.
  2138. */
  2139. do_action( "created_{$taxonomy}", $term_id, $tt_id );
  2140. return array(
  2141. 'term_id' => $term_id,
  2142. 'term_taxonomy_id' => $tt_id,
  2143. );
  2144. }
  2145. /**
  2146. * Create Term and Taxonomy Relationships.
  2147. *
  2148. * Relates an object (post, link etc) to a term and taxonomy type. Creates the
  2149. * term and taxonomy relationship if it doesn't already exist. Creates a term if
  2150. * it doesn't exist (using the slug).
  2151. *
  2152. * A relationship means that the term is grouped in or belongs to the taxonomy.
  2153. * A term has no meaning until it is given context by defining which taxonomy it
  2154. * exists under.
  2155. *
  2156. * @since 2.3.0
  2157. *
  2158. * @global wpdb $wpdb WordPress database abstraction object.
  2159. *
  2160. * @param int $object_id The object to relate to.
  2161. * @param string|int|array $terms A single term slug, single term id, or array of either term slugs or ids.
  2162. * Will replace all existing related terms in this taxonomy. Passing an
  2163. * empty value will remove all related terms.
  2164. * @param string $taxonomy The context in which to relate the term to the object.
  2165. * @param bool $append Optional. If false will delete difference of terms. Default false.
  2166. * @return array|WP_Error Term taxonomy IDs of the affected terms or WP_Error on failure.
  2167. */
  2168. function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
  2169. global $wpdb;
  2170. $object_id = (int) $object_id;
  2171. if ( ! taxonomy_exists( $taxonomy ) ) {
  2172. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  2173. }
  2174. if ( ! is_array( $terms ) ) {
  2175. $terms = array( $terms );
  2176. }
  2177. if ( ! $append ) {
  2178. $old_tt_ids = wp_get_object_terms(
  2179. $object_id,
  2180. $taxonomy,
  2181. array(
  2182. 'fields' => 'tt_ids',
  2183. 'orderby' => 'none',
  2184. 'update_term_meta_cache' => false,
  2185. )
  2186. );
  2187. } else {
  2188. $old_tt_ids = array();
  2189. }
  2190. $tt_ids = array();
  2191. $term_ids = array();
  2192. $new_tt_ids = array();
  2193. foreach ( (array) $terms as $term ) {
  2194. if ( '' === trim( $term ) ) {
  2195. continue;
  2196. }
  2197. $term_info = term_exists( $term, $taxonomy );
  2198. if ( ! $term_info ) {
  2199. // Skip if a non-existent term ID is passed.
  2200. if ( is_int( $term ) ) {
  2201. continue;
  2202. }
  2203. $term_info = wp_insert_term( $term, $taxonomy );
  2204. }
  2205. if ( is_wp_error( $term_info ) ) {
  2206. return $term_info;
  2207. }
  2208. $term_ids[] = $term_info['term_id'];
  2209. $tt_id = $term_info['term_taxonomy_id'];
  2210. $tt_ids[] = $tt_id;
  2211. if ( $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $tt_id ) ) ) {
  2212. continue;
  2213. }
  2214. /**
  2215. * Fires immediately before an object-term relationship is added.
  2216. *
  2217. * @since 2.9.0
  2218. * @since 4.7.0 Added the `$taxonomy` parameter.
  2219. *
  2220. * @param int $object_id Object ID.
  2221. * @param int $tt_id Term taxonomy ID.
  2222. * @param string $taxonomy Taxonomy slug.
  2223. */
  2224. do_action( 'add_term_relationship', $object_id, $tt_id, $taxonomy );
  2225. $wpdb->insert(
  2226. $wpdb->term_relationships,
  2227. array(
  2228. 'object_id' => $object_id,
  2229. 'term_taxonomy_id' => $tt_id,
  2230. )
  2231. );
  2232. /**
  2233. * Fires immediately after an object-term relationship is added.
  2234. *
  2235. * @since 2.9.0
  2236. * @since 4.7.0 Added the `$taxonomy` parameter.
  2237. *
  2238. * @param int $object_id Object ID.
  2239. * @param int $tt_id Term taxonomy ID.
  2240. * @param string $taxonomy Taxonomy slug.
  2241. */
  2242. do_action( 'added_term_relationship', $object_id, $tt_id, $taxonomy );
  2243. $new_tt_ids[] = $tt_id;
  2244. }
  2245. if ( $new_tt_ids ) {
  2246. wp_update_term_count( $new_tt_ids, $taxonomy );
  2247. }
  2248. if ( ! $append ) {
  2249. $delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
  2250. if ( $delete_tt_ids ) {
  2251. $in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
  2252. $delete_term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT tt.term_id FROM $wpdb->term_taxonomy AS tt WHERE tt.taxonomy = %s AND tt.term_taxonomy_id IN ($in_delete_tt_ids)", $taxonomy ) );
  2253. $delete_term_ids = array_map( 'intval', $delete_term_ids );
  2254. $remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
  2255. if ( is_wp_error( $remove ) ) {
  2256. return $remove;
  2257. }
  2258. }
  2259. }
  2260. $t = get_taxonomy( $taxonomy );
  2261. if ( ! $append && isset( $t->sort ) && $t->sort ) {
  2262. $values = array();
  2263. $term_order = 0;
  2264. $final_tt_ids = wp_get_object_terms(
  2265. $object_id,
  2266. $taxonomy,
  2267. array(
  2268. 'fields' => 'tt_ids',
  2269. 'update_term_meta_cache' => false,
  2270. )
  2271. );
  2272. foreach ( $tt_ids as $tt_id ) {
  2273. if ( in_array( $tt_id, $final_tt_ids ) ) {
  2274. $values[] = $wpdb->prepare( '(%d, %d, %d)', $object_id, $tt_id, ++$term_order );
  2275. }
  2276. }
  2277. if ( $values ) {
  2278. if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . join( ',', $values ) . ' ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)' ) ) {
  2279. return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database.' ), $wpdb->last_error );
  2280. }
  2281. }
  2282. }
  2283. wp_cache_delete( $object_id, $taxonomy . '_relationships' );
  2284. wp_cache_delete( 'last_changed', 'terms' );
  2285. /**
  2286. * Fires after an object's terms have been set.
  2287. *
  2288. * @since 2.8.0
  2289. *
  2290. * @param int $object_id Object ID.
  2291. * @param array $terms An array of object terms.
  2292. * @param array $tt_ids An array of term taxonomy IDs.
  2293. * @param string $taxonomy Taxonomy slug.
  2294. * @param bool $append Whether to append new terms to the old terms.
  2295. * @param array $old_tt_ids Old array of term taxonomy IDs.
  2296. */
  2297. do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
  2298. return $tt_ids;
  2299. }
  2300. /**
  2301. * Add term(s) associated with a given object.
  2302. *
  2303. * @since 3.6.0
  2304. *
  2305. * @param int $object_id The ID of the object to which the terms will be added.
  2306. * @param string|int|array $terms The slug(s) or ID(s) of the term(s) to add.
  2307. * @param array|string $taxonomy Taxonomy name.
  2308. * @return array|WP_Error Term taxonomy IDs of the affected terms.
  2309. */
  2310. function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
  2311. return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
  2312. }
  2313. /**
  2314. * Remove term(s) associated with a given object.
  2315. *
  2316. * @since 3.6.0
  2317. *
  2318. * @global wpdb $wpdb WordPress database abstraction object.
  2319. *
  2320. * @param int $object_id The ID of the object from which the terms will be removed.
  2321. * @param string|int|array $terms The slug(s) or ID(s) of the term(s) to remove.
  2322. * @param array|string $taxonomy Taxonomy name.
  2323. * @return bool|WP_Error True on success, false or WP_Error on failure.
  2324. */
  2325. function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
  2326. global $wpdb;
  2327. $object_id = (int) $object_id;
  2328. if ( ! taxonomy_exists( $taxonomy ) ) {
  2329. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  2330. }
  2331. if ( ! is_array( $terms ) ) {
  2332. $terms = array( $terms );
  2333. }
  2334. $tt_ids = array();
  2335. foreach ( (array) $terms as $term ) {
  2336. if ( '' === trim( $term ) ) {
  2337. continue;
  2338. }
  2339. $term_info = term_exists( $term, $taxonomy );
  2340. if ( ! $term_info ) {
  2341. // Skip if a non-existent term ID is passed.
  2342. if ( is_int( $term ) ) {
  2343. continue;
  2344. }
  2345. }
  2346. if ( is_wp_error( $term_info ) ) {
  2347. return $term_info;
  2348. }
  2349. $tt_ids[] = $term_info['term_taxonomy_id'];
  2350. }
  2351. if ( $tt_ids ) {
  2352. $in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
  2353. /**
  2354. * Fires immediately before an object-term relationship is deleted.
  2355. *
  2356. * @since 2.9.0
  2357. * @since 4.7.0 Added the `$taxonomy` parameter.
  2358. *
  2359. * @param int $object_id Object ID.
  2360. * @param array $tt_ids An array of term taxonomy IDs.
  2361. * @param string $taxonomy Taxonomy slug.
  2362. */
  2363. do_action( 'delete_term_relationships', $object_id, $tt_ids, $taxonomy );
  2364. $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
  2365. wp_cache_delete( $object_id, $taxonomy . '_relationships' );
  2366. wp_cache_delete( 'last_changed', 'terms' );
  2367. /**
  2368. * Fires immediately after an object-term relationship is deleted.
  2369. *
  2370. * @since 2.9.0
  2371. * @since 4.7.0 Added the `$taxonomy` parameter.
  2372. *
  2373. * @param int $object_id Object ID.
  2374. * @param array $tt_ids An array of term taxonomy IDs.
  2375. * @param string $taxonomy Taxonomy slug.
  2376. */
  2377. do_action( 'deleted_term_relationships', $object_id, $tt_ids, $taxonomy );
  2378. wp_update_term_count( $tt_ids, $taxonomy );
  2379. return (bool) $deleted;
  2380. }
  2381. return false;
  2382. }
  2383. /**
  2384. * Will make slug unique, if it isn't already.
  2385. *
  2386. * The `$slug` has to be unique global to every taxonomy, meaning that one
  2387. * taxonomy term can't have a matching slug with another taxonomy term. Each
  2388. * slug has to be globally unique for every taxonomy.
  2389. *
  2390. * The way this works is that if the taxonomy that the term belongs to is
  2391. * hierarchical and has a parent, it will append that parent to the $slug.
  2392. *
  2393. * If that still doesn't return a unique slug, then it tries to append a number
  2394. * until it finds a number that is truly unique.
  2395. *
  2396. * The only purpose for `$term` is for appending a parent, if one exists.
  2397. *
  2398. * @since 2.3.0
  2399. *
  2400. * @global wpdb $wpdb WordPress database abstraction object.
  2401. *
  2402. * @param string $slug The string that will be tried for a unique slug.
  2403. * @param object $term The term object that the `$slug` will belong to.
  2404. * @return string Will return a true unique slug.
  2405. */
  2406. function wp_unique_term_slug( $slug, $term ) {
  2407. global $wpdb;
  2408. $needs_suffix = true;
  2409. $original_slug = $slug;
  2410. // As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
  2411. if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
  2412. $needs_suffix = false;
  2413. }
  2414. /*
  2415. * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
  2416. * by incorporating parent slugs.
  2417. */
  2418. $parent_suffix = '';
  2419. if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
  2420. $the_parent = $term->parent;
  2421. while ( ! empty( $the_parent ) ) {
  2422. $parent_term = get_term( $the_parent, $term->taxonomy );
  2423. if ( is_wp_error( $parent_term ) || empty( $parent_term ) ) {
  2424. break;
  2425. }
  2426. $parent_suffix .= '-' . $parent_term->slug;
  2427. if ( ! term_exists( $slug . $parent_suffix ) ) {
  2428. break;
  2429. }
  2430. if ( empty( $parent_term->parent ) ) {
  2431. break;
  2432. }
  2433. $the_parent = $parent_term->parent;
  2434. }
  2435. }
  2436. // If we didn't get a unique slug, try appending a number to make it unique.
  2437. /**
  2438. * Filters whether the proposed unique term slug is bad.
  2439. *
  2440. * @since 4.3.0
  2441. *
  2442. * @param bool $needs_suffix Whether the slug needs to be made unique with a suffix.
  2443. * @param string $slug The slug.
  2444. * @param object $term Term object.
  2445. */
  2446. if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
  2447. if ( $parent_suffix ) {
  2448. $slug .= $parent_suffix;
  2449. }
  2450. if ( ! empty( $term->term_id ) ) {
  2451. $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
  2452. } else {
  2453. $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
  2454. }
  2455. if ( $wpdb->get_var( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
  2456. $num = 2;
  2457. do {
  2458. $alt_slug = $slug . "-$num";
  2459. $num++;
  2460. $slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
  2461. } while ( $slug_check );
  2462. $slug = $alt_slug;
  2463. }
  2464. }
  2465. /**
  2466. * Filters the unique term slug.
  2467. *
  2468. * @since 4.3.0
  2469. *
  2470. * @param string $slug Unique term slug.
  2471. * @param object $term Term object.
  2472. * @param string $original_slug Slug originally passed to the function for testing.
  2473. */
  2474. return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
  2475. }
  2476. /**
  2477. * Update term based on arguments provided.
  2478. *
  2479. * The $args will indiscriminately override all values with the same field name.
  2480. * Care must be taken to not override important information need to update or
  2481. * update will fail (or perhaps create a new term, neither would be acceptable).
  2482. *
  2483. * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
  2484. * defined in $args already.
  2485. *
  2486. * 'alias_of' will create a term group, if it doesn't already exist, and update
  2487. * it for the $term.
  2488. *
  2489. * If the 'slug' argument in $args is missing, then the 'name' in $args will be
  2490. * used. It should also be noted that if you set 'slug' and it isn't unique then
  2491. * a WP_Error will be passed back. If you don't pass any slug, then a unique one
  2492. * will be created for you.
  2493. *
  2494. * For what can be overrode in `$args`, check the term scheme can contain and stay
  2495. * away from the term keys.
  2496. *
  2497. * @since 2.3.0
  2498. *
  2499. * @global wpdb $wpdb WordPress database abstraction object.
  2500. *
  2501. * @param int $term_id The ID of the term
  2502. * @param string $taxonomy The context in which to relate the term to the object.
  2503. * @param array|string $args Optional. Array of get_terms() arguments. Default empty array.
  2504. * @return array|WP_Error Returns Term ID and Taxonomy Term ID
  2505. */
  2506. function wp_update_term( $term_id, $taxonomy, $args = array() ) {
  2507. global $wpdb;
  2508. if ( ! taxonomy_exists( $taxonomy ) ) {
  2509. return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
  2510. }
  2511. $term_id = (int) $term_id;
  2512. // First, get all of the original args
  2513. $term = get_term( $term_id, $taxonomy );
  2514. if ( is_wp_error( $term ) ) {
  2515. return $term;
  2516. }
  2517. if ( ! $term ) {
  2518. return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
  2519. }
  2520. $term = (array) $term->data;
  2521. // Escape data pulled from DB.
  2522. $term = wp_slash( $term );
  2523. // Merge old and new args with new args overwriting old ones.
  2524. $args = array_merge( $term, $args );
  2525. $defaults = array(
  2526. 'alias_of' => '',
  2527. 'description' => '',
  2528. 'parent' => 0,
  2529. 'slug' => '',
  2530. );
  2531. $args = wp_parse_args( $args, $defaults );
  2532. $args = sanitize_term( $args, $taxonomy, 'db' );
  2533. $parsed_args = $args;
  2534. // expected_slashed ($name)
  2535. $name = wp_unslash( $args['name'] );
  2536. $description = wp_unslash( $args['description'] );
  2537. $parsed_args['name'] = $name;
  2538. $parsed_args['description'] = $description;
  2539. if ( '' === trim( $name ) ) {
  2540. return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
  2541. }
  2542. if ( $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
  2543. return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
  2544. }
  2545. $empty_slug = false;
  2546. if ( empty( $args['slug'] ) ) {
  2547. $empty_slug = true;
  2548. $slug = sanitize_title( $name );
  2549. } else {
  2550. $slug = $args['slug'];
  2551. }
  2552. $parsed_args['slug'] = $slug;
  2553. $term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
  2554. if ( $args['alias_of'] ) {
  2555. $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
  2556. if ( ! empty( $alias->term_group ) ) {
  2557. // The alias we want is already in a group, so let's use that one.
  2558. $term_group = $alias->term_group;
  2559. } elseif ( ! empty( $alias->term_id ) ) {
  2560. /*
  2561. * The alias is not in a group, so we create a new one
  2562. * and add the alias to it.
  2563. */
  2564. $term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
  2565. wp_update_term(
  2566. $alias->term_id,
  2567. $taxonomy,
  2568. array(
  2569. 'term_group' => $term_group,
  2570. )
  2571. );
  2572. }
  2573. $parsed_args['term_group'] = $term_group;
  2574. }
  2575. /**
  2576. * Filters the term parent.
  2577. *
  2578. * Hook to this filter to see if it will cause a hierarchy loop.
  2579. *
  2580. * @since 3.1.0
  2581. *
  2582. * @param int $parent ID of the parent term.
  2583. * @param int $term_id Term ID.
  2584. * @param string $taxonomy Taxonomy slug.
  2585. * @param array $parsed_args An array of potentially altered update arguments for the given term.
  2586. * @param array $args An array of update arguments for the given term.
  2587. */
  2588. $parent = (int) apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
  2589. // Check for duplicate slug
  2590. $duplicate = get_term_by( 'slug', $slug, $taxonomy );
  2591. if ( $duplicate && $duplicate->term_id !== $term_id ) {
  2592. // If an empty slug was passed or the parent changed, reset the slug to something unique.
  2593. // Otherwise, bail.
  2594. if ( $empty_slug || ( $parent !== (int) $term['parent'] ) ) {
  2595. $slug = wp_unique_term_slug( $slug, (object) $args );
  2596. } else {
  2597. /* translators: %s: Taxonomy term slug. */
  2598. return new WP_Error( 'duplicate_term_slug', sprintf( __( 'The slug &#8220;%s&#8221; is already in use by another term.' ), $slug ) );
  2599. }
  2600. }
  2601. $tt_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
  2602. // Check whether this is a shared term that needs splitting.
  2603. $_term_id = _split_shared_term( $term_id, $tt_id );
  2604. if ( ! is_wp_error( $_term_id ) ) {
  2605. $term_id = $_term_id;
  2606. }
  2607. /**
  2608. * Fires immediately before the given terms are edited.
  2609. *
  2610. * @since 2.9.0
  2611. *
  2612. * @param int $term_id Term ID.
  2613. * @param string $taxonomy Taxonomy slug.
  2614. */
  2615. do_action( 'edit_terms', $term_id, $taxonomy );
  2616. $data = compact( 'name', 'slug', 'term_group' );
  2617. /**
  2618. * Filters term data before it is updated in the database.
  2619. *
  2620. * @since 4.7.0
  2621. *
  2622. * @param array $data Term data to be updated.
  2623. * @param int $term_id Term ID.
  2624. * @param string $taxonomy Taxonomy slug.
  2625. * @param array $args Arguments passed to wp_update_term().
  2626. */
  2627. $data = apply_filters( 'wp_update_term_data', $data, $term_id, $taxonomy, $args );
  2628. $wpdb->update( $wpdb->terms, $data, compact( 'term_id' ) );
  2629. if ( empty( $slug ) ) {
  2630. $slug = sanitize_title( $name, $term_id );
  2631. $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
  2632. }
  2633. /**
  2634. * Fires immediately after the given terms are edited.
  2635. *
  2636. * @since 2.9.0
  2637. *
  2638. * @param int $term_id Term ID
  2639. * @param string $taxonomy Taxonomy slug.
  2640. */
  2641. do_action( 'edited_terms', $term_id, $taxonomy );
  2642. /**
  2643. * Fires immediate before a term-taxonomy relationship is updated.
  2644. *
  2645. * @since 2.9.0
  2646. *
  2647. * @param int $tt_id Term taxonomy ID.
  2648. * @param string $taxonomy Taxonomy slug.
  2649. */
  2650. do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
  2651. $wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
  2652. /**
  2653. * Fires immediately after a term-taxonomy relationship is updated.
  2654. *
  2655. * @since 2.9.0
  2656. *
  2657. * @param int $tt_id Term taxonomy ID.
  2658. * @param string $taxonomy Taxonomy slug.
  2659. */
  2660. do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
  2661. /**
  2662. * Fires after a term has been updated, but before the term cache has been cleaned.
  2663. *
  2664. * @since 2.3.0
  2665. *
  2666. * @param int $term_id Term ID.
  2667. * @param int $tt_id Term taxonomy ID.
  2668. * @param string $taxonomy Taxonomy slug.
  2669. */
  2670. do_action( 'edit_term', $term_id, $tt_id, $taxonomy );
  2671. /**
  2672. * Fires after a term in a specific taxonomy has been updated, but before the term
  2673. * cache has been cleaned.
  2674. *
  2675. * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
  2676. *
  2677. * @since 2.3.0
  2678. *
  2679. * @param int $term_id Term ID.
  2680. * @param int $tt_id Term taxonomy ID.
  2681. */
  2682. do_action( "edit_{$taxonomy}", $term_id, $tt_id );
  2683. /** This filter is documented in wp-includes/taxonomy.php */
  2684. $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
  2685. clean_term_cache( $term_id, $taxonomy );
  2686. /**
  2687. * Fires after a term has been updated, and the term cache has been cleaned.
  2688. *
  2689. * @since 2.3.0
  2690. *
  2691. * @param int $term_id Term ID.
  2692. * @param int $tt_id Term taxonomy ID.
  2693. * @param string $taxonomy Taxonomy slug.
  2694. */
  2695. do_action( 'edited_term', $term_id, $tt_id, $taxonomy );
  2696. /**
  2697. * Fires after a term for a specific taxonomy has been updated, and the term
  2698. * cache has been cleaned.
  2699. *
  2700. * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
  2701. *
  2702. * @since 2.3.0
  2703. *
  2704. * @param int $term_id Term ID.
  2705. * @param int $tt_id Term taxonomy ID.
  2706. */
  2707. do_action( "edited_{$taxonomy}", $term_id, $tt_id );
  2708. return array(
  2709. 'term_id' => $term_id,
  2710. 'term_taxonomy_id' => $tt_id,
  2711. );
  2712. }
  2713. /**
  2714. * Enable or disable term counting.
  2715. *
  2716. * @since 2.5.0
  2717. *
  2718. * @staticvar bool $_defer
  2719. *
  2720. * @param bool $defer Optional. Enable if true, disable if false.
  2721. * @return bool Whether term counting is enabled or disabled.
  2722. */
  2723. function wp_defer_term_counting( $defer = null ) {
  2724. static $_defer = false;
  2725. if ( is_bool( $defer ) ) {
  2726. $_defer = $defer;
  2727. // flush any deferred counts
  2728. if ( ! $defer ) {
  2729. wp_update_term_count( null, null, true );
  2730. }
  2731. }
  2732. return $_defer;
  2733. }
  2734. /**
  2735. * Updates the amount of terms in taxonomy.
  2736. *
  2737. * If there is a taxonomy callback applied, then it will be called for updating
  2738. * the count.
  2739. *
  2740. * The default action is to count what the amount of terms have the relationship
  2741. * of term ID. Once that is done, then update the database.
  2742. *
  2743. * @since 2.3.0
  2744. *
  2745. * @staticvar array $_deferred
  2746. *
  2747. * @param int|array $terms The term_taxonomy_id of the terms.
  2748. * @param string $taxonomy The context of the term.
  2749. * @param bool $do_deferred Whether to flush the deferred term counts too. Default false.
  2750. * @return bool If no terms will return false, and if successful will return true.
  2751. */
  2752. function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) {
  2753. static $_deferred = array();
  2754. if ( $do_deferred ) {
  2755. foreach ( (array) array_keys( $_deferred ) as $tax ) {
  2756. wp_update_term_count_now( $_deferred[ $tax ], $tax );
  2757. unset( $_deferred[ $tax ] );
  2758. }
  2759. }
  2760. if ( empty( $terms ) ) {
  2761. return false;
  2762. }
  2763. if ( ! is_array( $terms ) ) {
  2764. $terms = array( $terms );
  2765. }
  2766. if ( wp_defer_term_counting() ) {
  2767. if ( ! isset( $_deferred[ $taxonomy ] ) ) {
  2768. $_deferred[ $taxonomy ] = array();
  2769. }
  2770. $_deferred[ $taxonomy ] = array_unique( array_merge( $_deferred[ $taxonomy ], $terms ) );
  2771. return true;
  2772. }
  2773. return wp_update_term_count_now( $terms, $taxonomy );
  2774. }
  2775. /**
  2776. * Perform term count update immediately.
  2777. *
  2778. * @since 2.5.0
  2779. *
  2780. * @param array $terms The term_taxonomy_id of terms to update.
  2781. * @param string $taxonomy The context of the term.
  2782. * @return true Always true when complete.
  2783. */
  2784. function wp_update_term_count_now( $terms, $taxonomy ) {
  2785. $terms = array_map( 'intval', $terms );
  2786. $taxonomy = get_taxonomy( $taxonomy );
  2787. if ( ! empty( $taxonomy->update_count_callback ) ) {
  2788. call_user_func( $taxonomy->update_count_callback, $terms, $taxonomy );
  2789. } else {
  2790. $object_types = (array) $taxonomy->object_type;
  2791. foreach ( $object_types as &$object_type ) {
  2792. if ( 0 === strpos( $object_type, 'attachment:' ) ) {
  2793. list( $object_type ) = explode( ':', $object_type );
  2794. }
  2795. }
  2796. if ( $object_types == array_filter( $object_types, 'post_type_exists' ) ) {
  2797. // Only post types are attached to this taxonomy
  2798. _update_post_term_count( $terms, $taxonomy );
  2799. } else {
  2800. // Default count updater
  2801. _update_generic_term_count( $terms, $taxonomy );
  2802. }
  2803. }
  2804. clean_term_cache( $terms, '', false );
  2805. return true;
  2806. }
  2807. //
  2808. // Cache
  2809. //
  2810. /**
  2811. * Removes the taxonomy relationship to terms from the cache.
  2812. *
  2813. * Will remove the entire taxonomy relationship containing term `$object_id`. The
  2814. * term IDs have to exist within the taxonomy `$object_type` for the deletion to
  2815. * take place.
  2816. *
  2817. * @since 2.3.0
  2818. *
  2819. * @global bool $_wp_suspend_cache_invalidation
  2820. *
  2821. * @see get_object_taxonomies() for more on $object_type.
  2822. *
  2823. * @param int|array $object_ids Single or list of term object ID(s).
  2824. * @param array|string $object_type The taxonomy object type.
  2825. */
  2826. function clean_object_term_cache( $object_ids, $object_type ) {
  2827. global $_wp_suspend_cache_invalidation;
  2828. if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
  2829. return;
  2830. }
  2831. if ( ! is_array( $object_ids ) ) {
  2832. $object_ids = array( $object_ids );
  2833. }
  2834. $taxonomies = get_object_taxonomies( $object_type );
  2835. foreach ( $object_ids as $id ) {
  2836. foreach ( $taxonomies as $taxonomy ) {
  2837. wp_cache_delete( $id, "{$taxonomy}_relationships" );
  2838. }
  2839. }
  2840. /**
  2841. * Fires after the object term cache has been cleaned.
  2842. *
  2843. * @since 2.5.0
  2844. *
  2845. * @param array $object_ids An array of object IDs.
  2846. * @param string $object_type Object type.
  2847. */
  2848. do_action( 'clean_object_term_cache', $object_ids, $object_type );
  2849. }
  2850. /**
  2851. * Will remove all of the term ids from the cache.
  2852. *
  2853. * @since 2.3.0
  2854. *
  2855. * @global wpdb $wpdb WordPress database abstraction object.
  2856. * @global bool $_wp_suspend_cache_invalidation
  2857. *
  2858. * @param int|array $ids Single or list of Term IDs.
  2859. * @param string $taxonomy Optional. Can be empty and will assume `tt_ids`, else will use for context.
  2860. * Default empty.
  2861. * @param bool $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
  2862. * term object caches (false). Default true.
  2863. */
  2864. function clean_term_cache( $ids, $taxonomy = '', $clean_taxonomy = true ) {
  2865. global $wpdb, $_wp_suspend_cache_invalidation;
  2866. if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
  2867. return;
  2868. }
  2869. if ( ! is_array( $ids ) ) {
  2870. $ids = array( $ids );
  2871. }
  2872. $taxonomies = array();
  2873. // If no taxonomy, assume tt_ids.
  2874. if ( empty( $taxonomy ) ) {
  2875. $tt_ids = array_map( 'intval', $ids );
  2876. $tt_ids = implode( ', ', $tt_ids );
  2877. $terms = $wpdb->get_results( "SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)" );
  2878. $ids = array();
  2879. foreach ( (array) $terms as $term ) {
  2880. $taxonomies[] = $term->taxonomy;
  2881. $ids[] = $term->term_id;
  2882. wp_cache_delete( $term->term_id, 'terms' );
  2883. }
  2884. $taxonomies = array_unique( $taxonomies );
  2885. } else {
  2886. $taxonomies = array( $taxonomy );
  2887. foreach ( $taxonomies as $taxonomy ) {
  2888. foreach ( $ids as $id ) {
  2889. wp_cache_delete( $id, 'terms' );
  2890. }
  2891. }
  2892. }
  2893. foreach ( $taxonomies as $taxonomy ) {
  2894. if ( $clean_taxonomy ) {
  2895. clean_taxonomy_cache( $taxonomy );
  2896. }
  2897. /**
  2898. * Fires once after each taxonomy's term cache has been cleaned.
  2899. *
  2900. * @since 2.5.0
  2901. * @since 4.5.0 Added the `$clean_taxonomy` parameter.
  2902. *
  2903. * @param array $ids An array of term IDs.
  2904. * @param string $taxonomy Taxonomy slug.
  2905. * @param bool $clean_taxonomy Whether or not to clean taxonomy-wide caches
  2906. */
  2907. do_action( 'clean_term_cache', $ids, $taxonomy, $clean_taxonomy );
  2908. }
  2909. wp_cache_set( 'last_changed', microtime(), 'terms' );
  2910. }
  2911. /**
  2912. * Clean the caches for a taxonomy.
  2913. *
  2914. * @since 4.9.0
  2915. *
  2916. * @param string $taxonomy Taxonomy slug.
  2917. */
  2918. function clean_taxonomy_cache( $taxonomy ) {
  2919. wp_cache_delete( 'all_ids', $taxonomy );
  2920. wp_cache_delete( 'get', $taxonomy );
  2921. // Regenerate cached hierarchy.
  2922. delete_option( "{$taxonomy}_children" );
  2923. _get_term_hierarchy( $taxonomy );
  2924. /**
  2925. * Fires after a taxonomy's caches have been cleaned.
  2926. *
  2927. * @since 4.9.0
  2928. *
  2929. * @param string $taxonomy Taxonomy slug.
  2930. */
  2931. do_action( 'clean_taxonomy_cache', $taxonomy );
  2932. }
  2933. /**
  2934. * Retrieves the cached term objects for the given object ID.
  2935. *
  2936. * Upstream functions (like get_the_terms() and is_object_in_term()) are
  2937. * responsible for populating the object-term relationship cache. The current
  2938. * function only fetches relationship data that is already in the cache.
  2939. *
  2940. * @since 2.3.0
  2941. * @since 4.7.0 Returns a `WP_Error` object if there's an error with
  2942. * any of the matched terms.
  2943. *
  2944. * @param int $id Term object ID, for example a post, comment, or user ID.
  2945. * @param string $taxonomy Taxonomy name.
  2946. * @return bool|WP_Term[]|WP_Error Array of `WP_Term` objects, if cached.
  2947. * False if cache is empty for `$taxonomy` and `$id`.
  2948. * WP_Error if get_term() returns an error object for any term.
  2949. */
  2950. function get_object_term_cache( $id, $taxonomy ) {
  2951. $_term_ids = wp_cache_get( $id, "{$taxonomy}_relationships" );
  2952. // We leave the priming of relationship caches to upstream functions.
  2953. if ( false === $_term_ids ) {
  2954. return false;
  2955. }
  2956. // Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
  2957. $term_ids = array();
  2958. foreach ( $_term_ids as $term_id ) {
  2959. if ( is_numeric( $term_id ) ) {
  2960. $term_ids[] = intval( $term_id );
  2961. } elseif ( isset( $term_id->term_id ) ) {
  2962. $term_ids[] = intval( $term_id->term_id );
  2963. }
  2964. }
  2965. // Fill the term objects.
  2966. _prime_term_caches( $term_ids );
  2967. $terms = array();
  2968. foreach ( $term_ids as $term_id ) {
  2969. $term = get_term( $term_id, $taxonomy );
  2970. if ( is_wp_error( $term ) ) {
  2971. return $term;
  2972. }
  2973. $terms[] = $term;
  2974. }
  2975. return $terms;
  2976. }
  2977. /**
  2978. * Updates the cache for the given term object ID(s).
  2979. *
  2980. * Note: Due to performance concerns, great care should be taken to only update
  2981. * term caches when necessary. Processing time can increase exponentially depending
  2982. * on both the number of passed term IDs and the number of taxonomies those terms
  2983. * belong to.
  2984. *
  2985. * Caches will only be updated for terms not already cached.
  2986. *
  2987. * @since 2.3.0
  2988. *
  2989. * @param string|int[] $object_ids Comma-separated list or array of term object IDs.
  2990. * @param string|string[] $object_type The taxonomy object type or array of the same.
  2991. * @return void|false False if all of the terms in `$object_ids` are already cached.
  2992. */
  2993. function update_object_term_cache( $object_ids, $object_type ) {
  2994. if ( empty( $object_ids ) ) {
  2995. return;
  2996. }
  2997. if ( ! is_array( $object_ids ) ) {
  2998. $object_ids = explode( ',', $object_ids );
  2999. }
  3000. $object_ids = array_map( 'intval', $object_ids );
  3001. $taxonomies = get_object_taxonomies( $object_type );
  3002. $ids = array();
  3003. foreach ( (array) $object_ids as $id ) {
  3004. foreach ( $taxonomies as $taxonomy ) {
  3005. if ( false === wp_cache_get( $id, "{$taxonomy}_relationships" ) ) {
  3006. $ids[] = $id;
  3007. break;
  3008. }
  3009. }
  3010. }
  3011. if ( empty( $ids ) ) {
  3012. return false;
  3013. }
  3014. $terms = wp_get_object_terms(
  3015. $ids,
  3016. $taxonomies,
  3017. array(
  3018. 'fields' => 'all_with_object_id',
  3019. 'orderby' => 'name',
  3020. 'update_term_meta_cache' => false,
  3021. )
  3022. );
  3023. $object_terms = array();
  3024. foreach ( (array) $terms as $term ) {
  3025. $object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
  3026. }
  3027. foreach ( $ids as $id ) {
  3028. foreach ( $taxonomies as $taxonomy ) {
  3029. if ( ! isset( $object_terms[ $id ][ $taxonomy ] ) ) {
  3030. if ( ! isset( $object_terms[ $id ] ) ) {
  3031. $object_terms[ $id ] = array();
  3032. }
  3033. $object_terms[ $id ][ $taxonomy ] = array();
  3034. }
  3035. }
  3036. }
  3037. foreach ( $object_terms as $id => $value ) {
  3038. foreach ( $value as $taxonomy => $terms ) {
  3039. wp_cache_add( $id, $terms, "{$taxonomy}_relationships" );
  3040. }
  3041. }
  3042. }
  3043. /**
  3044. * Updates Terms to Taxonomy in cache.
  3045. *
  3046. * @since 2.3.0
  3047. *
  3048. * @param array $terms List of term objects to change.
  3049. * @param string $taxonomy Optional. Update Term to this taxonomy in cache. Default empty.
  3050. */
  3051. function update_term_cache( $terms, $taxonomy = '' ) {
  3052. foreach ( (array) $terms as $term ) {
  3053. // Create a copy in case the array was passed by reference.
  3054. $_term = clone $term;
  3055. // Object ID should not be cached.
  3056. unset( $_term->object_id );
  3057. wp_cache_add( $term->term_id, $_term, 'terms' );
  3058. }
  3059. }
  3060. //
  3061. // Private
  3062. //
  3063. /**
  3064. * Retrieves children of taxonomy as Term IDs.
  3065. *
  3066. * @access private
  3067. * @since 2.3.0
  3068. *
  3069. * @param string $taxonomy Taxonomy name.
  3070. * @return array Empty if $taxonomy isn't hierarchical or returns children as Term IDs.
  3071. */
  3072. function _get_term_hierarchy( $taxonomy ) {
  3073. if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
  3074. return array();
  3075. }
  3076. $children = get_option( "{$taxonomy}_children" );
  3077. if ( is_array( $children ) ) {
  3078. return $children;
  3079. }
  3080. $children = array();
  3081. $terms = get_terms(
  3082. array(
  3083. 'taxonomy' => $taxonomy,
  3084. 'get' => 'all',
  3085. 'orderby' => 'id',
  3086. 'fields' => 'id=>parent',
  3087. 'update_term_meta_cache' => false,
  3088. )
  3089. );
  3090. foreach ( $terms as $term_id => $parent ) {
  3091. if ( $parent > 0 ) {
  3092. $children[ $parent ][] = $term_id;
  3093. }
  3094. }
  3095. update_option( "{$taxonomy}_children", $children );
  3096. return $children;
  3097. }
  3098. /**
  3099. * Get the subset of $terms that are descendants of $term_id.
  3100. *
  3101. * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
  3102. * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
  3103. *
  3104. * @access private
  3105. * @since 2.3.0
  3106. *
  3107. * @param int $term_id The ancestor term: all returned terms should be descendants of `$term_id`.
  3108. * @param array $terms The set of terms - either an array of term objects or term IDs - from which those that
  3109. * are descendants of $term_id will be chosen.
  3110. * @param string $taxonomy The taxonomy which determines the hierarchy of the terms.
  3111. * @param array $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
  3112. * track of found terms when recursing the hierarchy. The array of located ancestors is used
  3113. * to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
  3114. * with 1 as value. Default empty array.
  3115. * @return array|WP_Error The subset of $terms that are descendants of $term_id.
  3116. */
  3117. function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
  3118. $empty_array = array();
  3119. if ( empty( $terms ) ) {
  3120. return $empty_array;
  3121. }
  3122. $term_id = (int) $term_id;
  3123. $term_list = array();
  3124. $has_children = _get_term_hierarchy( $taxonomy );
  3125. if ( $term_id && ! isset( $has_children[ $term_id ] ) ) {
  3126. return $empty_array;
  3127. }
  3128. // Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
  3129. if ( empty( $ancestors ) ) {
  3130. $ancestors[ $term_id ] = 1;
  3131. }
  3132. foreach ( (array) $terms as $term ) {
  3133. $use_id = false;
  3134. if ( ! is_object( $term ) ) {
  3135. $term = get_term( $term, $taxonomy );
  3136. if ( is_wp_error( $term ) ) {
  3137. return $term;
  3138. }
  3139. $use_id = true;
  3140. }
  3141. // Don't recurse if we've already identified the term as a child - this indicates a loop.
  3142. if ( isset( $ancestors[ $term->term_id ] ) ) {
  3143. continue;
  3144. }
  3145. if ( (int) $term->parent === $term_id ) {
  3146. if ( $use_id ) {
  3147. $term_list[] = $term->term_id;
  3148. } else {
  3149. $term_list[] = $term;
  3150. }
  3151. if ( ! isset( $has_children[ $term->term_id ] ) ) {
  3152. continue;
  3153. }
  3154. $ancestors[ $term->term_id ] = 1;
  3155. $children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors );
  3156. if ( $children ) {
  3157. $term_list = array_merge( $term_list, $children );
  3158. }
  3159. }
  3160. }
  3161. return $term_list;
  3162. }
  3163. /**
  3164. * Add count of children to parent count.
  3165. *
  3166. * Recalculates term counts by including items from child terms. Assumes all
  3167. * relevant children are already in the $terms argument.
  3168. *
  3169. * @access private
  3170. * @since 2.3.0
  3171. *
  3172. * @global wpdb $wpdb WordPress database abstraction object.
  3173. *
  3174. * @param array $terms List of term objects (passed by reference).
  3175. * @param string $taxonomy Term context.
  3176. */
  3177. function _pad_term_counts( &$terms, $taxonomy ) {
  3178. global $wpdb;
  3179. // This function only works for hierarchical taxonomies like post categories.
  3180. if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
  3181. return;
  3182. }
  3183. $term_hier = _get_term_hierarchy( $taxonomy );
  3184. if ( empty( $term_hier ) ) {
  3185. return;
  3186. }
  3187. $term_items = array();
  3188. $terms_by_id = array();
  3189. $term_ids = array();
  3190. foreach ( (array) $terms as $key => $term ) {
  3191. $terms_by_id[ $term->term_id ] = & $terms[ $key ];
  3192. $term_ids[ $term->term_taxonomy_id ] = $term->term_id;
  3193. }
  3194. // Get the object and term ids and stick them in a lookup table.
  3195. $tax_obj = get_taxonomy( $taxonomy );
  3196. $object_types = esc_sql( $tax_obj->object_type );
  3197. $results = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_keys( $term_ids ) ) . ") AND post_type IN ('" . implode( "', '", $object_types ) . "') AND post_status = 'publish'" );
  3198. foreach ( $results as $row ) {
  3199. $id = $term_ids[ $row->term_taxonomy_id ];
  3200. $term_items[ $id ][ $row->object_id ] = isset( $term_items[ $id ][ $row->object_id ] ) ? ++$term_items[ $id ][ $row->object_id ] : 1;
  3201. }
  3202. // Touch every ancestor's lookup row for each post in each term.
  3203. foreach ( $term_ids as $term_id ) {
  3204. $child = $term_id;
  3205. $ancestors = array();
  3206. while ( ! empty( $terms_by_id[ $child ] ) && $parent = $terms_by_id[ $child ]->parent ) {
  3207. $ancestors[] = $child;
  3208. if ( ! empty( $term_items[ $term_id ] ) ) {
  3209. foreach ( $term_items[ $term_id ] as $item_id => $touches ) {
  3210. $term_items[ $parent ][ $item_id ] = isset( $term_items[ $parent ][ $item_id ] ) ? ++$term_items[ $parent ][ $item_id ] : 1;
  3211. }
  3212. }
  3213. $child = $parent;
  3214. if ( in_array( $parent, $ancestors ) ) {
  3215. break;
  3216. }
  3217. }
  3218. }
  3219. // Transfer the touched cells.
  3220. foreach ( (array) $term_items as $id => $items ) {
  3221. if ( isset( $terms_by_id[ $id ] ) ) {
  3222. $terms_by_id[ $id ]->count = count( $items );
  3223. }
  3224. }
  3225. }
  3226. /**
  3227. * Adds any terms from the given IDs to the cache that do not already exist in cache.
  3228. *
  3229. * @since 4.6.0
  3230. * @access private
  3231. *
  3232. * @global wpdb $wpdb WordPress database abstraction object.
  3233. *
  3234. * @param array $term_ids Array of term IDs.
  3235. * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
  3236. */
  3237. function _prime_term_caches( $term_ids, $update_meta_cache = true ) {
  3238. global $wpdb;
  3239. $non_cached_ids = _get_non_cached_ids( $term_ids, 'terms' );
  3240. if ( ! empty( $non_cached_ids ) ) {
  3241. $fresh_terms = $wpdb->get_results( sprintf( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) );
  3242. update_term_cache( $fresh_terms, $update_meta_cache );
  3243. if ( $update_meta_cache ) {
  3244. update_termmeta_cache( $non_cached_ids );
  3245. }
  3246. }
  3247. }
  3248. //
  3249. // Default callbacks
  3250. //
  3251. /**
  3252. * Will update term count based on object types of the current taxonomy.
  3253. *
  3254. * Private function for the default callback for post_tag and category
  3255. * taxonomies.
  3256. *
  3257. * @access private
  3258. * @since 2.3.0
  3259. *
  3260. * @global wpdb $wpdb WordPress database abstraction object.
  3261. *
  3262. * @param array $terms List of Term taxonomy IDs.
  3263. * @param object $taxonomy Current taxonomy object of terms.
  3264. */
  3265. function _update_post_term_count( $terms, $taxonomy ) {
  3266. global $wpdb;
  3267. $object_types = (array) $taxonomy->object_type;
  3268. foreach ( $object_types as &$object_type ) {
  3269. list( $object_type ) = explode( ':', $object_type );
  3270. }
  3271. $object_types = array_unique( $object_types );
  3272. $check_attachments = array_search( 'attachment', $object_types );
  3273. if ( false !== $check_attachments ) {
  3274. unset( $object_types[ $check_attachments ] );
  3275. $check_attachments = true;
  3276. }
  3277. if ( $object_types ) {
  3278. $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
  3279. }
  3280. foreach ( (array) $terms as $term ) {
  3281. $count = 0;
  3282. // Attachments can be 'inherit' status, we need to base count off the parent's status if so.
  3283. if ( $check_attachments ) {
  3284. $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
  3285. }
  3286. if ( $object_types ) {
  3287. // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
  3288. $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
  3289. }
  3290. /** This action is documented in wp-includes/taxonomy.php */
  3291. do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
  3292. $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
  3293. /** This action is documented in wp-includes/taxonomy.php */
  3294. do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
  3295. }
  3296. }
  3297. /**
  3298. * Will update term count based on number of objects.
  3299. *
  3300. * Default callback for the 'link_category' taxonomy.
  3301. *
  3302. * @since 3.3.0
  3303. *
  3304. * @global wpdb $wpdb WordPress database abstraction object.
  3305. *
  3306. * @param array $terms List of term taxonomy IDs.
  3307. * @param object $taxonomy Current taxonomy object of terms.
  3308. */
  3309. function _update_generic_term_count( $terms, $taxonomy ) {
  3310. global $wpdb;
  3311. foreach ( (array) $terms as $term ) {
  3312. $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
  3313. /** This action is documented in wp-includes/taxonomy.php */
  3314. do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
  3315. $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
  3316. /** This action is documented in wp-includes/taxonomy.php */
  3317. do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
  3318. }
  3319. }
  3320. /**
  3321. * Create a new term for a term_taxonomy item that currently shares its term
  3322. * with another term_taxonomy.
  3323. *
  3324. * @ignore
  3325. * @since 4.2.0
  3326. * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
  3327. * `$term_taxonomy_id` can now accept objects.
  3328. *
  3329. * @global wpdb $wpdb WordPress database abstraction object.
  3330. *
  3331. * @param int|object $term_id ID of the shared term, or the shared term object.
  3332. * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
  3333. * (corresponding to a row from the term_taxonomy table).
  3334. * @param bool $record Whether to record data about the split term in the options table. The recording
  3335. * process has the potential to be resource-intensive, so during batch operations
  3336. * it can be beneficial to skip inline recording and do it just once, after the
  3337. * batch is processed. Only set this to `false` if you know what you are doing.
  3338. * Default: true.
  3339. * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
  3340. * database schema), `$term_id` is returned. When the term is successfully split, the
  3341. * new term_id is returned. A WP_Error is returned for miscellaneous errors.
  3342. */
  3343. function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
  3344. global $wpdb;
  3345. if ( is_object( $term_id ) ) {
  3346. $shared_term = $term_id;
  3347. $term_id = intval( $shared_term->term_id );
  3348. }
  3349. if ( is_object( $term_taxonomy_id ) ) {
  3350. $term_taxonomy = $term_taxonomy_id;
  3351. $term_taxonomy_id = intval( $term_taxonomy->term_taxonomy_id );
  3352. }
  3353. // If there are no shared term_taxonomy rows, there's nothing to do here.
  3354. $shared_tt_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy tt WHERE tt.term_id = %d AND tt.term_taxonomy_id != %d", $term_id, $term_taxonomy_id ) );
  3355. if ( ! $shared_tt_count ) {
  3356. return $term_id;
  3357. }
  3358. /*
  3359. * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
  3360. * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
  3361. */
  3362. $check_term_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
  3363. if ( $check_term_id !== $term_id ) {
  3364. return $check_term_id;
  3365. }
  3366. // Pull up data about the currently shared slug, which we'll use to populate the new one.
  3367. if ( empty( $shared_term ) ) {
  3368. $shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
  3369. }
  3370. $new_term_data = array(
  3371. 'name' => $shared_term->name,
  3372. 'slug' => $shared_term->slug,
  3373. 'term_group' => $shared_term->term_group,
  3374. );
  3375. if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
  3376. return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
  3377. }
  3378. $new_term_id = (int) $wpdb->insert_id;
  3379. // Update the existing term_taxonomy to point to the newly created term.
  3380. $wpdb->update(
  3381. $wpdb->term_taxonomy,
  3382. array( 'term_id' => $new_term_id ),
  3383. array( 'term_taxonomy_id' => $term_taxonomy_id )
  3384. );
  3385. // Reassign child terms to the new parent.
  3386. if ( empty( $term_taxonomy ) ) {
  3387. $term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
  3388. }
  3389. $children_tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE parent = %d AND taxonomy = %s", $term_id, $term_taxonomy->taxonomy ) );
  3390. if ( ! empty( $children_tt_ids ) ) {
  3391. foreach ( $children_tt_ids as $child_tt_id ) {
  3392. $wpdb->update(
  3393. $wpdb->term_taxonomy,
  3394. array( 'parent' => $new_term_id ),
  3395. array( 'term_taxonomy_id' => $child_tt_id )
  3396. );
  3397. clean_term_cache( (int) $child_tt_id, '', false );
  3398. }
  3399. } else {
  3400. // If the term has no children, we must force its taxonomy cache to be rebuilt separately.
  3401. clean_term_cache( $new_term_id, $term_taxonomy->taxonomy, false );
  3402. }
  3403. clean_term_cache( $term_id, $term_taxonomy->taxonomy, false );
  3404. /*
  3405. * Taxonomy cache clearing is delayed to avoid race conditions that may occur when
  3406. * regenerating the taxonomy's hierarchy tree.
  3407. */
  3408. $taxonomies_to_clean = array( $term_taxonomy->taxonomy );
  3409. // Clean the cache for term taxonomies formerly shared with the current term.
  3410. $shared_term_taxonomies = $wpdb->get_col( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
  3411. $taxonomies_to_clean = array_merge( $taxonomies_to_clean, $shared_term_taxonomies );
  3412. foreach ( $taxonomies_to_clean as $taxonomy_to_clean ) {
  3413. clean_taxonomy_cache( $taxonomy_to_clean );
  3414. }
  3415. // Keep a record of term_ids that have been split, keyed by old term_id. See wp_get_split_term().
  3416. if ( $record ) {
  3417. $split_term_data = get_option( '_split_terms', array() );
  3418. if ( ! isset( $split_term_data[ $term_id ] ) ) {
  3419. $split_term_data[ $term_id ] = array();
  3420. }
  3421. $split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
  3422. update_option( '_split_terms', $split_term_data );
  3423. }
  3424. // If we've just split the final shared term, set the "finished" flag.
  3425. $shared_terms_exist = $wpdb->get_results(
  3426. "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
  3427. LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
  3428. GROUP BY t.term_id
  3429. HAVING term_tt_count > 1
  3430. LIMIT 1"
  3431. );
  3432. if ( ! $shared_terms_exist ) {
  3433. update_option( 'finished_splitting_shared_terms', true );
  3434. }
  3435. /**
  3436. * Fires after a previously shared taxonomy term is split into two separate terms.
  3437. *
  3438. * @since 4.2.0
  3439. *
  3440. * @param int $term_id ID of the formerly shared term.
  3441. * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
  3442. * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
  3443. * @param string $taxonomy Taxonomy for the split term.
  3444. */
  3445. do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
  3446. return $new_term_id;
  3447. }
  3448. /**
  3449. * Splits a batch of shared taxonomy terms.
  3450. *
  3451. * @since 4.3.0
  3452. *
  3453. * @global wpdb $wpdb WordPress database abstraction object.
  3454. */
  3455. function _wp_batch_split_terms() {
  3456. global $wpdb;
  3457. $lock_name = 'term_split.lock';
  3458. // Try to lock.
  3459. $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
  3460. if ( ! $lock_result ) {
  3461. $lock_result = get_option( $lock_name );
  3462. // Bail if we were unable to create a lock, or if the existing lock is still valid.
  3463. if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
  3464. wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
  3465. return;
  3466. }
  3467. }
  3468. // Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
  3469. update_option( $lock_name, time() );
  3470. // Get a list of shared terms (those with more than one associated row in term_taxonomy).
  3471. $shared_terms = $wpdb->get_results(
  3472. "SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
  3473. LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
  3474. GROUP BY t.term_id
  3475. HAVING term_tt_count > 1
  3476. LIMIT 10"
  3477. );
  3478. // No more terms, we're done here.
  3479. if ( ! $shared_terms ) {
  3480. update_option( 'finished_splitting_shared_terms', true );
  3481. delete_option( $lock_name );
  3482. return;
  3483. }
  3484. // Shared terms found? We'll need to run this script again.
  3485. wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
  3486. // Rekey shared term array for faster lookups.
  3487. $_shared_terms = array();
  3488. foreach ( $shared_terms as $shared_term ) {
  3489. $term_id = intval( $shared_term->term_id );
  3490. $_shared_terms[ $term_id ] = $shared_term;
  3491. }
  3492. $shared_terms = $_shared_terms;
  3493. // Get term taxonomy data for all shared terms.
  3494. $shared_term_ids = implode( ',', array_keys( $shared_terms ) );
  3495. $shared_tts = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
  3496. // Split term data recording is slow, so we do it just once, outside the loop.
  3497. $split_term_data = get_option( '_split_terms', array() );
  3498. $skipped_first_term = array();
  3499. $taxonomies = array();
  3500. foreach ( $shared_tts as $shared_tt ) {
  3501. $term_id = intval( $shared_tt->term_id );
  3502. // Don't split the first tt belonging to a given term_id.
  3503. if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
  3504. $skipped_first_term[ $term_id ] = 1;
  3505. continue;
  3506. }
  3507. if ( ! isset( $split_term_data[ $term_id ] ) ) {
  3508. $split_term_data[ $term_id ] = array();
  3509. }
  3510. // Keep track of taxonomies whose hierarchies need flushing.
  3511. if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
  3512. $taxonomies[ $shared_tt->taxonomy ] = 1;
  3513. }
  3514. // Split the term.
  3515. $split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
  3516. }
  3517. // Rebuild the cached hierarchy for each affected taxonomy.
  3518. foreach ( array_keys( $taxonomies ) as $tax ) {
  3519. delete_option( "{$tax}_children" );
  3520. _get_term_hierarchy( $tax );
  3521. }
  3522. update_option( '_split_terms', $split_term_data );
  3523. delete_option( $lock_name );
  3524. }
  3525. /**
  3526. * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
  3527. * check that it's still scheduled while we haven't finished splitting terms.
  3528. *
  3529. * @ignore
  3530. * @since 4.3.0
  3531. */
  3532. function _wp_check_for_scheduled_split_terms() {
  3533. if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
  3534. wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
  3535. }
  3536. }
  3537. /**
  3538. * Check default categories when a term gets split to see if any of them need to be updated.
  3539. *
  3540. * @ignore
  3541. * @since 4.2.0
  3542. *
  3543. * @param int $term_id ID of the formerly shared term.
  3544. * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
  3545. * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
  3546. * @param string $taxonomy Taxonomy for the split term.
  3547. */
  3548. function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
  3549. if ( 'category' !== $taxonomy ) {
  3550. return;
  3551. }
  3552. foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
  3553. if ( $term_id === (int) get_option( $option, -1 ) ) {
  3554. update_option( $option, $new_term_id );
  3555. }
  3556. }
  3557. }
  3558. /**
  3559. * Check menu items when a term gets split to see if any of them need to be updated.
  3560. *
  3561. * @ignore
  3562. * @since 4.2.0
  3563. *
  3564. * @global wpdb $wpdb WordPress database abstraction object.
  3565. *
  3566. * @param int $term_id ID of the formerly shared term.
  3567. * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
  3568. * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
  3569. * @param string $taxonomy Taxonomy for the split term.
  3570. */
  3571. function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
  3572. global $wpdb;
  3573. $post_ids = $wpdb->get_col(
  3574. $wpdb->prepare(
  3575. "SELECT m1.post_id
  3576. FROM {$wpdb->postmeta} AS m1
  3577. INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
  3578. INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
  3579. WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
  3580. AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = %s )
  3581. AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
  3582. $taxonomy,
  3583. $term_id
  3584. )
  3585. );
  3586. if ( $post_ids ) {
  3587. foreach ( $post_ids as $post_id ) {
  3588. update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
  3589. }
  3590. }
  3591. }
  3592. /**
  3593. * If the term being split is a nav_menu, change associations.
  3594. *
  3595. * @ignore
  3596. * @since 4.3.0
  3597. *
  3598. * @param int $term_id ID of the formerly shared term.
  3599. * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
  3600. * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
  3601. * @param string $taxonomy Taxonomy for the split term.
  3602. */
  3603. function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
  3604. if ( 'nav_menu' !== $taxonomy ) {
  3605. return;
  3606. }
  3607. // Update menu locations.
  3608. $locations = get_nav_menu_locations();
  3609. foreach ( $locations as $location => $menu_id ) {
  3610. if ( $term_id === $menu_id ) {
  3611. $locations[ $location ] = $new_term_id;
  3612. }
  3613. }
  3614. set_theme_mod( 'nav_menu_locations', $locations );
  3615. }
  3616. /**
  3617. * Get data about terms that previously shared a single term_id, but have since been split.
  3618. *
  3619. * @since 4.2.0
  3620. *
  3621. * @param int $old_term_id Term ID. This is the old, pre-split term ID.
  3622. * @return array Array of new term IDs, keyed by taxonomy.
  3623. */
  3624. function wp_get_split_terms( $old_term_id ) {
  3625. $split_terms = get_option( '_split_terms', array() );
  3626. $terms = array();
  3627. if ( isset( $split_terms[ $old_term_id ] ) ) {
  3628. $terms = $split_terms[ $old_term_id ];
  3629. }
  3630. return $terms;
  3631. }
  3632. /**
  3633. * Get the new term ID corresponding to a previously split term.
  3634. *
  3635. * @since 4.2.0
  3636. *
  3637. * @param int $old_term_id Term ID. This is the old, pre-split term ID.
  3638. * @param string $taxonomy Taxonomy that the term belongs to.
  3639. * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
  3640. * the new term_id will be returned. If no previously split term is found matching
  3641. * the parameters, returns false.
  3642. */
  3643. function wp_get_split_term( $old_term_id, $taxonomy ) {
  3644. $split_terms = wp_get_split_terms( $old_term_id );
  3645. $term_id = false;
  3646. if ( isset( $split_terms[ $taxonomy ] ) ) {
  3647. $term_id = (int) $split_terms[ $taxonomy ];
  3648. }
  3649. return $term_id;
  3650. }
  3651. /**
  3652. * Determine whether a term is shared between multiple taxonomies.
  3653. *
  3654. * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or
  3655. * other delays in upgrade routines may cause shared terms to remain.
  3656. *
  3657. * @since 4.4.0
  3658. *
  3659. * @param int $term_id Term ID.
  3660. * @return bool Returns false if a term is not shared between multiple taxonomies or
  3661. * if splitting shared taxonomy terms is finished.
  3662. */
  3663. function wp_term_is_shared( $term_id ) {
  3664. global $wpdb;
  3665. if ( get_option( 'finished_splitting_shared_terms' ) ) {
  3666. return false;
  3667. }
  3668. $tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
  3669. return $tt_count > 1;
  3670. }
  3671. /**
  3672. * Generate a permalink for a taxonomy term archive.
  3673. *
  3674. * @since 2.5.0
  3675. *
  3676. * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
  3677. *
  3678. * @param object|int|string $term The term object, ID, or slug whose link will be retrieved.
  3679. * @param string $taxonomy Optional. Taxonomy. Default empty.
  3680. * @return string|WP_Error URL of the taxonomy term archive on success, WP_Error if term does not exist.
  3681. */
  3682. function get_term_link( $term, $taxonomy = '' ) {
  3683. global $wp_rewrite;
  3684. if ( ! is_object( $term ) ) {
  3685. if ( is_int( $term ) ) {
  3686. $term = get_term( $term, $taxonomy );
  3687. } else {
  3688. $term = get_term_by( 'slug', $term, $taxonomy );
  3689. }
  3690. }
  3691. if ( ! is_object( $term ) ) {
  3692. $term = new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
  3693. }
  3694. if ( is_wp_error( $term ) ) {
  3695. return $term;
  3696. }
  3697. $taxonomy = $term->taxonomy;
  3698. $termlink = $wp_rewrite->get_extra_permastruct( $taxonomy );
  3699. /**
  3700. * Filters the permalink structure for a terms before token replacement occurs.
  3701. *
  3702. * @since 4.9.0
  3703. *
  3704. * @param string $termlink The permalink structure for the term's taxonomy.
  3705. * @param WP_Term $term The term object.
  3706. */
  3707. $termlink = apply_filters( 'pre_term_link', $termlink, $term );
  3708. $slug = $term->slug;
  3709. $t = get_taxonomy( $taxonomy );
  3710. if ( empty( $termlink ) ) {
  3711. if ( 'category' === $taxonomy ) {
  3712. $termlink = '?cat=' . $term->term_id;
  3713. } elseif ( $t->query_var ) {
  3714. $termlink = "?$t->query_var=$slug";
  3715. } else {
  3716. $termlink = "?taxonomy=$taxonomy&term=$slug";
  3717. }
  3718. $termlink = home_url( $termlink );
  3719. } else {
  3720. if ( $t->rewrite['hierarchical'] ) {
  3721. $hierarchical_slugs = array();
  3722. $ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
  3723. foreach ( (array) $ancestors as $ancestor ) {
  3724. $ancestor_term = get_term( $ancestor, $taxonomy );
  3725. $hierarchical_slugs[] = $ancestor_term->slug;
  3726. }
  3727. $hierarchical_slugs = array_reverse( $hierarchical_slugs );
  3728. $hierarchical_slugs[] = $slug;
  3729. $termlink = str_replace( "%$taxonomy%", implode( '/', $hierarchical_slugs ), $termlink );
  3730. } else {
  3731. $termlink = str_replace( "%$taxonomy%", $slug, $termlink );
  3732. }
  3733. $termlink = home_url( user_trailingslashit( $termlink, 'category' ) );
  3734. }
  3735. // Back Compat filters.
  3736. if ( 'post_tag' === $taxonomy ) {
  3737. /**
  3738. * Filters the tag link.
  3739. *
  3740. * @since 2.3.0
  3741. * @deprecated 2.5.0 Use 'term_link' instead.
  3742. *
  3743. * @param string $termlink Tag link URL.
  3744. * @param int $term_id Term ID.
  3745. */
  3746. $termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
  3747. } elseif ( 'category' === $taxonomy ) {
  3748. /**
  3749. * Filters the category link.
  3750. *
  3751. * @since 1.5.0
  3752. * @deprecated 2.5.0 Use 'term_link' instead.
  3753. *
  3754. * @param string $termlink Category link URL.
  3755. * @param int $term_id Term ID.
  3756. */
  3757. $termlink = apply_filters( 'category_link', $termlink, $term->term_id );
  3758. }
  3759. /**
  3760. * Filters the term link.
  3761. *
  3762. * @since 2.5.0
  3763. *
  3764. * @param string $termlink Term link URL.
  3765. * @param object $term Term object.
  3766. * @param string $taxonomy Taxonomy slug.
  3767. */
  3768. return apply_filters( 'term_link', $termlink, $term, $taxonomy );
  3769. }
  3770. /**
  3771. * Display the taxonomies of a post with available options.
  3772. *
  3773. * This function can be used within the loop to display the taxonomies for a
  3774. * post without specifying the Post ID. You can also use it outside the Loop to
  3775. * display the taxonomies for a specific post.
  3776. *
  3777. * @since 2.5.0
  3778. *
  3779. * @param array $args {
  3780. * Arguments about which post to use and how to format the output. Shares all of the arguments
  3781. * supported by get_the_taxonomies(), in addition to the following.
  3782. *
  3783. * @type int|WP_Post $post Post ID or object to get taxonomies of. Default current post.
  3784. * @type string $before Displays before the taxonomies. Default empty string.
  3785. * @type string $sep Separates each taxonomy. Default is a space.
  3786. * @type string $after Displays after the taxonomies. Default empty string.
  3787. * }
  3788. */
  3789. function the_taxonomies( $args = array() ) {
  3790. $defaults = array(
  3791. 'post' => 0,
  3792. 'before' => '',
  3793. 'sep' => ' ',
  3794. 'after' => '',
  3795. );
  3796. $parsed_args = wp_parse_args( $args, $defaults );
  3797. echo $parsed_args['before'] . join( $parsed_args['sep'], get_the_taxonomies( $parsed_args['post'], $parsed_args ) ) . $parsed_args['after'];
  3798. }
  3799. /**
  3800. * Retrieve all taxonomies associated with a post.
  3801. *
  3802. * This function can be used within the loop. It will also return an array of
  3803. * the taxonomies with links to the taxonomy and name.
  3804. *
  3805. * @since 2.5.0
  3806. *
  3807. * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
  3808. * @param array $args {
  3809. * Optional. Arguments about how to format the list of taxonomies. Default empty array.
  3810. *
  3811. * @type string $template Template for displaying a taxonomy label and list of terms.
  3812. * Default is "Label: Terms."
  3813. * @type string $term_template Template for displaying a single term in the list. Default is the term name
  3814. * linked to its archive.
  3815. * }
  3816. * @return array List of taxonomies.
  3817. */
  3818. function get_the_taxonomies( $post = 0, $args = array() ) {
  3819. $post = get_post( $post );
  3820. $args = wp_parse_args(
  3821. $args,
  3822. array(
  3823. /* translators: %s: Taxonomy label, %l: List of terms formatted as per $term_template. */
  3824. 'template' => __( '%s: %l.' ),
  3825. 'term_template' => '<a href="%1$s">%2$s</a>',
  3826. )
  3827. );
  3828. $taxonomies = array();
  3829. if ( ! $post ) {
  3830. return $taxonomies;
  3831. }
  3832. foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
  3833. $t = (array) get_taxonomy( $taxonomy );
  3834. if ( empty( $t['label'] ) ) {
  3835. $t['label'] = $taxonomy;
  3836. }
  3837. if ( empty( $t['args'] ) ) {
  3838. $t['args'] = array();
  3839. }
  3840. if ( empty( $t['template'] ) ) {
  3841. $t['template'] = $args['template'];
  3842. }
  3843. if ( empty( $t['term_template'] ) ) {
  3844. $t['term_template'] = $args['term_template'];
  3845. }
  3846. $terms = get_object_term_cache( $post->ID, $taxonomy );
  3847. if ( false === $terms ) {
  3848. $terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
  3849. }
  3850. $links = array();
  3851. foreach ( $terms as $term ) {
  3852. $links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
  3853. }
  3854. if ( $links ) {
  3855. $taxonomies[ $taxonomy ] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
  3856. }
  3857. }
  3858. return $taxonomies;
  3859. }
  3860. /**
  3861. * Retrieve all taxonomies of a post with just the names.
  3862. *
  3863. * @since 2.5.0
  3864. *
  3865. * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
  3866. * @return array An array of all taxonomy names for the given post.
  3867. */
  3868. function get_post_taxonomies( $post = 0 ) {
  3869. $post = get_post( $post );
  3870. return get_object_taxonomies( $post );
  3871. }
  3872. /**
  3873. * Determine if the given object is associated with any of the given terms.
  3874. *
  3875. * The given terms are checked against the object's terms' term_ids, names and slugs.
  3876. * Terms given as integers will only be checked against the object's terms' term_ids.
  3877. * If no terms are given, determines if object is associated with any terms in the given taxonomy.
  3878. *
  3879. * @since 2.7.0
  3880. *
  3881. * @param int $object_id ID of the object (post ID, link ID, ...).
  3882. * @param string $taxonomy Single taxonomy name.
  3883. * @param int|string|array $terms Optional. Term term_id, name, slug or array of said. Default null.
  3884. * @return bool|WP_Error WP_Error on input error.
  3885. */
  3886. function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
  3887. $object_id = (int) $object_id;
  3888. if ( ! $object_id ) {
  3889. return new WP_Error( 'invalid_object', __( 'Invalid object ID.' ) );
  3890. }
  3891. $object_terms = get_object_term_cache( $object_id, $taxonomy );
  3892. if ( false === $object_terms ) {
  3893. $object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
  3894. if ( is_wp_error( $object_terms ) ) {
  3895. return $object_terms;
  3896. }
  3897. wp_cache_set( $object_id, wp_list_pluck( $object_terms, 'term_id' ), "{$taxonomy}_relationships" );
  3898. }
  3899. if ( is_wp_error( $object_terms ) ) {
  3900. return $object_terms;
  3901. }
  3902. if ( empty( $object_terms ) ) {
  3903. return false;
  3904. }
  3905. if ( empty( $terms ) ) {
  3906. return ( ! empty( $object_terms ) );
  3907. }
  3908. $terms = (array) $terms;
  3909. $ints = array_filter( $terms, 'is_int' );
  3910. if ( $ints ) {
  3911. $strs = array_diff( $terms, $ints );
  3912. } else {
  3913. $strs =& $terms;
  3914. }
  3915. foreach ( $object_terms as $object_term ) {
  3916. // If term is an int, check against term_ids only.
  3917. if ( $ints && in_array( $object_term->term_id, $ints ) ) {
  3918. return true;
  3919. }
  3920. if ( $strs ) {
  3921. // Only check numeric strings against term_id, to avoid false matches due to type juggling.
  3922. $numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
  3923. if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
  3924. return true;
  3925. }
  3926. if ( in_array( $object_term->name, $strs ) ) {
  3927. return true;
  3928. }
  3929. if ( in_array( $object_term->slug, $strs ) ) {
  3930. return true;
  3931. }
  3932. }
  3933. }
  3934. return false;
  3935. }
  3936. /**
  3937. * Determine if the given object type is associated with the given taxonomy.
  3938. *
  3939. * @since 3.0.0
  3940. *
  3941. * @param string $object_type Object type string.
  3942. * @param string $taxonomy Single taxonomy name.
  3943. * @return bool True if object is associated with the taxonomy, otherwise false.
  3944. */
  3945. function is_object_in_taxonomy( $object_type, $taxonomy ) {
  3946. $taxonomies = get_object_taxonomies( $object_type );
  3947. if ( empty( $taxonomies ) ) {
  3948. return false;
  3949. }
  3950. return in_array( $taxonomy, $taxonomies );
  3951. }
  3952. /**
  3953. * Get an array of ancestor IDs for a given object.
  3954. *
  3955. * @since 3.1.0
  3956. * @since 4.1.0 Introduced the `$resource_type` argument.
  3957. *
  3958. * @param int $object_id Optional. The ID of the object. Default 0.
  3959. * @param string $object_type Optional. The type of object for which we'll be retrieving
  3960. * ancestors. Accepts a post type or a taxonomy name. Default empty.
  3961. * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
  3962. * or 'taxonomy'. Default empty.
  3963. * @return array An array of ancestors from lowest to highest in the hierarchy.
  3964. */
  3965. function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
  3966. $object_id = (int) $object_id;
  3967. $ancestors = array();
  3968. if ( empty( $object_id ) ) {
  3969. /** This filter is documented in wp-includes/taxonomy.php */
  3970. return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
  3971. }
  3972. if ( ! $resource_type ) {
  3973. if ( is_taxonomy_hierarchical( $object_type ) ) {
  3974. $resource_type = 'taxonomy';
  3975. } elseif ( post_type_exists( $object_type ) ) {
  3976. $resource_type = 'post_type';
  3977. }
  3978. }
  3979. if ( 'taxonomy' === $resource_type ) {
  3980. $term = get_term( $object_id, $object_type );
  3981. while ( ! is_wp_error( $term ) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors ) ) {
  3982. $ancestors[] = (int) $term->parent;
  3983. $term = get_term( $term->parent, $object_type );
  3984. }
  3985. } elseif ( 'post_type' === $resource_type ) {
  3986. $ancestors = get_post_ancestors( $object_id );
  3987. }
  3988. /**
  3989. * Filters a given object's ancestors.
  3990. *
  3991. * @since 3.1.0
  3992. * @since 4.1.1 Introduced the `$resource_type` parameter.
  3993. *
  3994. * @param array $ancestors An array of object ancestors.
  3995. * @param int $object_id Object ID.
  3996. * @param string $object_type Type of object.
  3997. * @param string $resource_type Type of resource $object_type is.
  3998. */
  3999. return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
  4000. }
  4001. /**
  4002. * Returns the term's parent's term_ID.
  4003. *
  4004. * @since 3.1.0
  4005. *
  4006. * @param int $term_id Term ID.
  4007. * @param string $taxonomy Taxonomy name.
  4008. * @return int|false False on error.
  4009. */
  4010. function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
  4011. $term = get_term( $term_id, $taxonomy );
  4012. if ( ! $term || is_wp_error( $term ) ) {
  4013. return false;
  4014. }
  4015. return (int) $term->parent;
  4016. }
  4017. /**
  4018. * Checks the given subset of the term hierarchy for hierarchy loops.
  4019. * Prevents loops from forming and breaks those that it finds.
  4020. *
  4021. * Attached to the {@see 'wp_update_term_parent'} filter.
  4022. *
  4023. * @since 3.1.0
  4024. *
  4025. * @param int $parent `term_id` of the parent for the term we're checking.
  4026. * @param int $term_id The term we're checking.
  4027. * @param string $taxonomy The taxonomy of the term we're checking.
  4028. *
  4029. * @return int The new parent for the term.
  4030. */
  4031. function wp_check_term_hierarchy_for_loops( $parent, $term_id, $taxonomy ) {
  4032. // Nothing fancy here - bail
  4033. if ( ! $parent ) {
  4034. return 0;
  4035. }
  4036. // Can't be its own parent.
  4037. if ( $parent === $term_id ) {
  4038. return 0;
  4039. }
  4040. // Now look for larger loops.
  4041. $loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) );
  4042. if ( ! $loop ) {
  4043. return $parent; // No loop
  4044. }
  4045. // Setting $parent to the given value causes a loop.
  4046. if ( isset( $loop[ $term_id ] ) ) {
  4047. return 0;
  4048. }
  4049. // There's a loop, but it doesn't contain $term_id. Break the loop.
  4050. foreach ( array_keys( $loop ) as $loop_member ) {
  4051. wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
  4052. }
  4053. return $parent;
  4054. }
  4055. /**
  4056. * Determines whether a taxonomy is considered "viewable".
  4057. *
  4058. * @since 5.1.0
  4059. *
  4060. * @param string|WP_Taxonomy $taxonomy Taxonomy name or object.
  4061. * @return bool Whether the taxonomy should be considered viewable.
  4062. */
  4063. function is_taxonomy_viewable( $taxonomy ) {
  4064. if ( is_scalar( $taxonomy ) ) {
  4065. $taxonomy = get_taxonomy( $taxonomy );
  4066. if ( ! $taxonomy ) {
  4067. return false;
  4068. }
  4069. }
  4070. return $taxonomy->publicly_queryable;
  4071. }
  4072. /**
  4073. * Sets the last changed time for the 'terms' cache group.
  4074. *
  4075. * @since 5.0.0
  4076. */
  4077. function wp_cache_set_terms_last_changed() {
  4078. wp_cache_set( 'last_changed', microtime(), 'terms' );
  4079. }
  4080. /**
  4081. * Aborts calls to term meta if it is not supported.
  4082. *
  4083. * @since 5.0.0
  4084. *
  4085. * @param mixed $check Skip-value for whether to proceed term meta function execution.
  4086. * @return mixed Original value of $check, or false if term meta is not supported.
  4087. */
  4088. function wp_check_term_meta_support_prefilter( $check ) {
  4089. if ( get_option( 'db_version' ) < 34370 ) {
  4090. return false;
  4091. }
  4092. return $check;
  4093. }