extractRequestParams(); $options = []; if ( isset( $params['options'] ) ) { $options = FormatJson::decode( $params['options'] ); if ( !is_object( $options ) ) { $this->dieWithError( 'apierror-categorytree-invalidjson', 'invalidjson' ); } $options = get_object_vars( $options ); } $title = CategoryTree::makeTitle( $params['category'] ); if ( !$title || $title->isExternal() ) { $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['category'] ) ] ); } $depth = isset( $options['depth'] ) ? (int)$options['depth'] : 1; $ct = new CategoryTree( $options ); $depth = CategoryTree::capDepth( $ct->getOption( 'mode' ), $depth ); $config = $this->getConfig(); $ctConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'categorytree' ); $html = $this->getHTML( $ct, $title, $depth, $ctConfig ); if ( $ctConfig->get( 'CategoryTreeHTTPCache' ) && $config->get( 'SquidMaxage' ) && $config->get( 'UseSquid' ) ) { if ( $config->get( 'UseESI' ) ) { $this->getRequest()->response()->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' ) . ', content="ESI/1.0"' ); $this->getMain()->setCacheMaxAge( 0 ); } else { $this->getMain()->setCacheMaxAge( $config->get( 'SquidMaxage' ) ); } // cache for anons only $this->getRequest()->response()->header( 'Vary: Accept-Encoding, Cookie' ); // TODO: purge the squid cache when a category page is invalidated } $this->getResult()->addContentValue( $this->getModuleName(), 'html', $html ); } /** * @param string $condition * * @return bool|null|string */ public function getConditionalRequestData( $condition ) { if ( $condition === 'last-modified' ) { $params = $this->extractRequestParams(); $title = CategoryTree::makeTitle( $params['category'] ); return wfGetDB( DB_REPLICA )->selectField( 'page', 'page_touched', [ 'page_namespace' => NS_CATEGORY, 'page_title' => $title->getDBkey(), ], __METHOD__ ); } } /** * Get category tree HTML for the given tree, title, depth and config * * @param CategoryTree $ct * @param Title $title * @param int $depth * @param Config $ctConfig Config for CategoryTree * @return string HTML */ private function getHTML( CategoryTree $ct, Title $title, $depth, Config $ctConfig ) { global $wgContLang, $wgMemc; $mckey = wfMemcKey( 'ajax-categorytree', md5( $title->getDBkey() ), md5( $ct->getOptionsAsCacheKey( $depth ) ), $this->getLanguage()->getCode(), $wgContLang->getExtraHashOptions(), $ctConfig->get( 'RenderHashAppend' ) ); $touched = $this->getConditionalRequestData( 'last-modified' ); if ( $touched ) { $mcvalue = $wgMemc->get( $mckey ); if ( $mcvalue && $touched <= $mcvalue['timestamp'] ) { $html = $mcvalue['value']; } } if ( !isset( $html ) ) { $html = $ct->renderChildren( $title, $depth ); $wgMemc->set( $mckey, [ 'timestamp' => wfTimestampNow(), 'value' => $html ], 86400 ); } return trim( $html ); } /** * @inheritDoc */ public function getAllowedParams() { return [ 'category' => [ ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true, ], 'options' => [ ApiBase::PARAM_TYPE => 'string', ], ]; } /** * @inheritDoc */ public function isInternal() { return true; } }